Browse Source

我还是不能放弃 el-focustrap

xiaochan 1 năm trước cách đây
mục cha
commit
f6b2026635

+ 0 - 1
package.json

@@ -24,7 +24,6 @@
     "element-plus": "file:deps/element-plus.2.0.4.1.tar.gz",
     "exceljs": "^4.4.0",
     "file-saver": "^2.0.5",
-    "focus-trap": "^7.5.4",
     "iconv-lite": "^0.6.3",
     "jquery": "^3.6.0",
     "js-cookie": "^3.0.0",

+ 6 - 6
src/components/cy/combo-grid/src/index.ts

@@ -2,7 +2,6 @@ import {ElTooltip, useFocusController, useFormItem, useFormItemInputId, useNames
 import {useCompRef} from "@/utils/useCompRef";
 import XEUtils, {filter, isArray, isEqual, isFunction, isNumber, isString} from "xe-utils";
 import {useResizeObserver} from "@vueuse/core";
-//@ts-ignore
 import {stringNotBlank} from "@/utils/blank-utils";
 import {listFilter} from "@/utils/list-utlis";
 //@ts-ignore
@@ -568,18 +567,19 @@ export default function UesComboGrid(props: CyComboGridOptionsV2, emits: any) {
         tableRef.value?.scrollToRow(selectData)
     }
 
-    function handleEsc(event: Event) {
-        function stopPropagation() {
-            event.preventDefault()
+    function handleEsc(event: KeyboardEvent) {
+
+        function handleStop() {
             event.stopPropagation();
+            event.preventDefault();
         }
 
         if (states.inputValue.length > 0) {
             states.inputValue = ''
-            stopPropagation()
+            handleStop()
         } else {
             if (expanded.value) {
-                stopPropagation()
+                handleStop()
             }
             expanded.value = false
         }

+ 0 - 1
src/components/cy/cy-monaco-editor/CyMonacoEditor.tsx

@@ -1,4 +1,3 @@
-// @ts-nocheck
 import {defineComponent, nextTick, onMounted, PropType, ref} from "vue";
 import useCreateMonaco, {XcIStandaloneCodeEditor} from "@/utils/monaco-utlis/useCreateMonaco";
 import sleep from "@/utils/sleep";

+ 55 - 45
src/components/cy/dialog/src/CyDialog.vue

@@ -1,7 +1,8 @@
 <script lang="ts">
 import {
   ElButton,
-  useZIndex
+  useZIndex,
+  ElFocusTrap
 } from 'element-plus'
 import {
   CyDialogProps,
@@ -10,14 +11,14 @@ import {
 } from "@/components/cy/dialog/src/useCyDialog";
 import './cy-dialog.scss'
 
-
 const COMPONENT_NAME = 'CyDialog'
 
 export default defineComponent({
   name: COMPONENT_NAME,
   componentName: COMPONENT_NAME,
   components: {
-    ElButton
+    ElButton,
+    ElFocusTrap
   },
   props: {...CyDialogProps},
   emits: ['closed'],
@@ -45,62 +46,71 @@ export default defineComponent({
     <div
         :class="[ ns.e('dialog')]"
     >
-      <div
-          ref="boxRef"
-          :style="boxStyle"
-          :class="[
+      <ElFocusTrap
+          loop
+          focus-start-el="container"
+          :trapped="true"
+          :focus-trap-el="containerRef"
+          @focusout-prevented="onFocusoutPrevented"
+          @release-requested="onCloseRequested"
+      >
+        <div
+            ref="boxRef"
+            :style="boxStyle"
+            :class="[
             ns.e('box'),
             ns.is('fullScreen' , fullScreen)
         ]"
 
-      >
-        <header
-            ref="headerRef"
-            :class="[
+        >
+          <header
+              ref="headerRef"
+              :class="[
             ns.e('header'),
           ]"
-        >
+          >
         <span>
            {{ title }}
         </span>
-        </header>
-        <div
-            ref="bodyRef"
-            :style="bodyStyle"
-            :class="[
+          </header>
+          <div
+              ref="bodyRef"
+              :style="bodyStyle"
+              :class="[
             ns.e('body'),
           ]"
-        >
-          <slot
-              :width="state.bodySize.width"
-              :height="state.bodySize.height"
-          />
-        </div>
-        <footer
-            ref="footerRef"
-            :class="[
+          >
+            <slot
+                :width="state.bodySize.width"
+                :height="state.bodySize.height"
+            />
+          </div>
+          <footer
+              ref="footerRef"
+              :class="[
             ns.e('footer'),
           ]"
-        >
-          <el-button
-              v-if="showCancelButton"
-              @click="handleCancel"
-              size="default"
           >
-            {{ cancelText ?? '取消' }}
-          </el-button>
-          <el-button
-              color="hsl(240 5.9% 10%)"
-              style="margin-right: 15px"
-              @click="handleConfirm"
-              size="default"
-              :loading="state.confirmLoading"
-              type="primary"
-          >
-            {{ confirmText ?? '确认' }}
-          </el-button>
-        </footer>
-      </div>
+            <el-button
+                v-if="showCancelButton"
+                @click="handleCancel"
+                size="default"
+            >
+              {{ cancelText ?? '取消' }}
+            </el-button>
+            <el-button
+                color="hsl(240 5.9% 10%)"
+                style="margin-right: 15px"
+                @click="handleConfirm"
+                size="default"
+                :loading="state.confirmLoading"
+                type="primary"
+            >
+              {{ confirmText ?? '确认' }}
+            </el-button>
+          </footer>
+        </div>
+      </ElFocusTrap>
     </div>
   </div>
 </template>

+ 4 - 41
src/components/cy/dialog/src/useCyDialog.ts

@@ -1,10 +1,8 @@
-import {ExtractPropTypes, ref, getCurrentInstance, reactive, computed, onMounted, nextTick} from 'vue'
+import {ExtractPropTypes, ref, getCurrentInstance, reactive, computed} from 'vue'
 import {useCyNamespace} from "@/utils/xiaochan-element-plus";
 import {useResizeObserver} from "@vueuse/core";
-import {useDraggable, useEscapeKeydown} from "element-plus";
+import {useDraggable} from "element-plus";
 import {ClosingMethod, compList} from "@/components/js-dialog-comp/useDialogToJs";
-import * as focusTrap from 'focus-trap';
-import {FocusTrap} from "focus-trap";
 
 export const CyDialogProps = {
     title: String,
@@ -48,10 +46,6 @@ export const CyDialogProps = {
         type: Boolean,
         default: true,
     },
-    closeOnPressEscape: {
-        type: Boolean,
-        default: false
-    }
 }
 
 export type IsCyDialog = ExtractPropTypes<typeof CyDialogProps>
@@ -125,7 +119,7 @@ export function UseCyDialog(props: IsCyDialog, emit: any) {
     useDraggable(boxRef, headerRef, draggable)
 
     function closed(closingMethod: ClosingMethod, val: any) {
-        emit('closed', closingMethod, val)
+        emit('closed')
     }
 
     async function handleConfirm() {
@@ -172,7 +166,6 @@ export function UseCyDialog(props: IsCyDialog, emit: any) {
         next(null)
     }
 
-
     function onFocusoutPrevented(event: CustomEvent) {
         if (event.detail?.focusReason === 'pointer') {
             event.preventDefault()
@@ -180,39 +173,9 @@ export function UseCyDialog(props: IsCyDialog, emit: any) {
     }
 
     function onCloseRequested() {
-        if (props.closeOnPressEscape) handleCancel()
+        handleCancel()
     }
 
-    let trap: FocusTrap | null = null
-
-    useEscapeKeydown(() => {
-        // 因为一开始就是激活状态,如果是暂停的话,就说名打开了其他的对话框
-        if (trap?.paused) {
-            return
-        }
-        if (trap?.active) {
-            onCloseRequested()
-        }
-    })
-
-    let activeElement = document.activeElement
-
-    onMounted(async () => {
-        trap = focusTrap.createFocusTrap(boxRef.value, {
-            escapeDeactivates: false,
-            allowOutsideClick: true,
-            clickOutsideDeactivates: true,
-            async checkCanFocusTrap() {
-                await nextTick()
-            },
-            //@ts-ignore
-            setReturnFocus() {
-                return activeElement
-            }
-        })
-        await nextTick();
-        trap.activate()
-    })
 
     return {
         ns,

+ 109 - 149
src/components/cy/message-box/src/index.vue

@@ -1,21 +1,20 @@
 <script setup lang="ts">
 import {
+  ElOverlay,
+  ElFocusTrap,
   useZIndex,
   ElInput,
   ElSelect,
   ElOption,
   ElIcon,
   useDraggable,
-  ElButton, useEscapeKeydown,
+  ElButton,
 } from "element-plus";
 import {nextTick, onMounted, ref, computed, watch} from "vue";
 import sleep from "@/utils/sleep";
 import {Close} from '@element-plus/icons-vue';
 import {useCompRef} from "@/utils/useCompRef";
 import {uuid} from '@/utils/getUuid';
-import * as focusTrap from 'focus-trap';
-import {FocusTrap} from "focus-trap";
-import usePromise from "@/utils/cy-use/usePromise";
 
 const props = defineProps({
   title: {
@@ -112,7 +111,8 @@ const props = defineProps({
 const emits = defineEmits(['cancel', 'confirm', 'vanish'])
 const visible = ref(false)
 const zIndex = ref(0)
-const rootRef = ref<HTMLDivElement | undefined>()
+const rootRef = ref<HTMLDivElement>()
+const focusStartRef = ref<HTMLElement>()
 const inputRef = ref()
 const headerRef = ref<HTMLElement>()
 const draggable = computed(() => props.draggable)
@@ -120,9 +120,6 @@ const confirmRef = useCompRef(ElButton)
 const dithering = ref(false)
 const inputValue = ref(props.inputDefaultValue)
 const errorMsg = ref('')
-const overlayRef = ref()
-let trap: FocusTrap
-
 const inputId = 'cy-message' + uuid(5, 62)
 
 const reboundClick = async () => {
@@ -178,7 +175,6 @@ const handelClose = (val: 'cancel' | 'confirm' | 'close') => {
     reboundClick();
     return;
   }
-  trap!.deactivate()
   visible.value = false
   if (val === 'confirm') {
     emits('confirm', {action: val, value: inputValue.value})
@@ -195,163 +191,139 @@ const onCloseRequested = () => {
   }
 }
 
-useDraggable(rootRef, headerRef, draggable)
-
-useEscapeKeydown(() => {
-  if (trap!.active) {
-    onCloseRequested()
+watch(() => visible.value, (val) => {
+  if (val) {
+    nextTick(() => {
+      if (props.showInput) {
+        focusStartRef.value = inputRef.value?.$el ?? rootRef.value
+        sleep(200).then(() => {
+          inputRef.value?.focus()
+        })
+      } else {
+        focusStartRef.value = confirmRef.value?.$el ?? rootRef.value
+      }
+    })
   }
 })
 
-let activeElement = document.activeElement
-
-const [animationPromise, animationEnter] = usePromise()
-
-async function handleAfterEnter() {
-  await nextTick()
-  animationEnter(true)
-}
+useDraggable(rootRef, headerRef, draggable)
 
 onMounted(async () => {
-  activeElement = document.activeElement
   props.setClose(() => {
     handelClose('close')
   })
   zIndex.value = useZIndex().nextZIndex()
   await nextTick()
-  // @ts-ignore
-  trap = focusTrap.createFocusTrap(rootRef.value, {
-    escapeDeactivates: false,
-    allowOutsideClick: true,
-    clickOutsideDeactivates: true,
-    checkCanFocusTrap() {
-      return animationPromise
-    },
-    setReturnFocus() {
-      return activeElement
-    },
-    initialFocus: () => {
-      if (props.showInput) {
-        inputRef.value.focus()
-      } else {
-        confirmRef.value.ref.focus()
-      }
-      return false
-    }
-  })
   await sleep(100);
   visible.value = true
-  trap.activate()
 })
 
 </script>
 
 <template>
-  <transition
-      name="overlay_background"
-  >
-    <div v-show="visible"
-         ref="overlayRef"
-         class="cy-message_overlay"
-         :style="{zIndex}">
-      <transition name="cy-enlarge"
-                  @afterEnter="handleAfterEnter()"
-                  @after-leave="emits('vanish')">
+  <transition name="overlay_background">
+    <ElOverlay v-show="visible"
+               overlay-class="cy-message_overlay"
+               :z-index="zIndex">
+      <transition name="cy-enlarge" @after-leave="emits('vanish')">
         <div role="dialog"
              v-show="visible"
              aria-modal="true"
              class="cy-overlay-message-box"
              @click="overlayClick">
-          <div :class="{'cy-message-box_dithering' : dithering}">
-            <div ref="rootRef"
-                 tabindex="-1"
-                 class="cy-message-box"
-                 @click.stop>
-              <header ref="headerRef">
-                <div class="cy-message-box_title">
-                  <el-icon v-if="props.icon"
-                           :size="24"
-                           :style="{color: props.iconColor}">
-                    <component :is="props.icon"/>
-                  </el-icon>
-                  {{ props.title }}
-                </div>
+          <ElFocusTrap loop
+                       :trapped="visible"
+                       :focus-trap-el="rootRef"
+                       :focus-start-el="focusStartRef"
+                       @release-requested="onCloseRequested">
+            <div :class="dithering ? 'cy-message-box_dithering' : ''">
+              <div ref="rootRef" tabindex="-1" class="cy-message-box" @click.stop>
+                <header ref="headerRef">
+                  <div class="cy-message-box_title">
+                    <el-icon v-if="props.icon"
+                             :size="24"
+                             :style="{color: props.iconColor}">
+                      <component :is="props.icon"/>
+                    </el-icon>
+                    {{ props.title }}
+                  </div>
 
-                <button @click.stop="handelClose('close')"
-                        v-if="props.showIcon"
-                        @keydown.prevent.enter="handelClose('close')">
-                  <el-icon>
-                    <Close/>
-                  </el-icon>
-                </button>
-
-              </header>
-              <div class="cy-message-box_body">
-                <div class="cy-message-box_message" v-if="props.message">
-                  <component :is="props.showInput ? 'label' : 'p'"
-                             v-if="props.dangerouslyUseHTMLString"
-                             v-html="props.message"
-                             :for="inputId"></component>
-                  <component :is="props.showInput ? 'label' : 'p'"
-                             v-else
-                             :for="inputId">
-                    {{ props.message }}
-                  </component>
-                </div>
-                <div v-if="props.showInput" style="padding-top: 15px">
-                  <el-select v-model="inputValue"
-                             v-if="props.selectOption"
-                             size="small"
-                             filterable
-                             style="width: 100%"
-                             allow-create
-                             :reserve-keyword="true"
-                             default-first-option
-                             :id="inputId"
-                             ref="inputRef">
-                    <el-option v-for="item in props.selectOption"
-                               :label="item.name"
-                               :value="item.code"
-                               :key="item.code"/>
-                  </el-select>
-                  <el-input v-model="inputValue"
-                            size="small"
-                            v-else
-                            @keydown.prevent.enter="handelClose('confirm')"
-                            :type="props.inputRows ? 'textarea' : 'text'"
-                            :rows="props.inputRows"
-                            :id="inputId"
-                            ref="inputRef"
-                            :maxlength="props.inputMaxLength"
-                            show-word-limit/>
-                  <div class="cy-message-box_error-msg"
-                       :style="{visibility: errorMsg ? 'visible' : 'hidden'}">
-                    {{ errorMsg }}
+                  <button @click.stop="handelClose('close')"
+                          v-if="props.showIcon"
+                          @keydown.prevent.enter="handelClose('close')">
+                    <el-icon>
+                      <Close/>
+                    </el-icon>
+                  </button>
+
+                </header>
+                <div class="cy-message-box_body">
+                  <div class="cy-message-box_message" v-if="props.message">
+                    <component :is="props.showInput ? 'label' : 'p'"
+                               v-if="props.dangerouslyUseHTMLString"
+                               v-html="props.message"
+                               :for="inputId"></component>
+                    <component :is="props.showInput ? 'label' : 'p'"
+                               v-else
+                               :for="inputId">
+                      {{ props.message }}
+                    </component>
+                  </div>
+                  <div v-if="props.showInput" style="padding-top: 15px">
+                    <el-select v-model="inputValue"
+                               v-if="props.selectOption"
+                               size="small"
+                               filterable
+                               style="width: 100%"
+                               allow-create
+                               :reserve-keyword="true"
+                               default-first-option
+                               :id="inputId"
+                               ref="inputRef">
+                      <el-option v-for="item in props.selectOption"
+                                 :label="item.name"
+                                 :value="item.code"
+                                 :key="item.code"/>
+                    </el-select>
+                    <el-input v-model="inputValue"
+                              size="small"
+                              v-else
+                              @keydown.prevent.enter="handelClose('confirm')"
+                              :type="props.inputRows ? 'textarea' : 'text'"
+                              :rows="props.inputRows"
+                              :id="inputId"
+                              ref="inputRef"
+                              :maxlength="props.inputMaxLength"
+                              show-word-limit/>
+                    <div class="cy-message-box_error-msg"
+                         :style="{visibility: errorMsg ? 'visible' : 'hidden'}">
+                      {{ errorMsg }}
+                    </div>
                   </div>
                 </div>
+                <footer>
+                  <el-button text
+                             v-if="props.showCancelButton"
+                             @click.stop="handelClose('cancel')"
+                             type="danger"
+                             @keydown.prevent.enter="handelClose('cancel')"
+                             class="cancel">
+                    {{ props.cancelButtonText }}
+                  </el-button>
+                  <el-button type="primary"
+                             class="confirm"
+                             @keydown.prevent.enter="handelClose('confirm')"
+                             ref="confirmRef"
+                             @click.stop="handelClose('confirm')">
+                    {{ props.confirmButtonText }}
+                  </el-button>
+                </footer>
               </div>
-              <footer>
-                <el-button text
-                           v-if="props.showCancelButton"
-                           @click.stop="handelClose('cancel')"
-                           type="danger"
-                           @keydown.prevent.enter="handelClose('cancel')"
-                           class="cancel">
-                  {{ props.cancelButtonText }}
-                </el-button>
-                <el-button type="primary"
-                           class="confirm"
-                           @keydown.prevent.enter="handelClose('confirm')"
-                           ref="confirmRef"
-                           @click.stop="handelClose('confirm')">
-                  {{ props.confirmButtonText }}
-                </el-button>
-              </footer>
             </div>
-          </div>
+          </ElFocusTrap>
         </div>
       </transition>
-    </div>
+    </ElOverlay>
   </transition>
 </template>
 
@@ -360,18 +332,6 @@ onMounted(async () => {
 
 $cy-message-box-radius: 13px;
 
-.cy-message_overlay {
-  position: fixed;
-  top: 0;
-  right: 0;
-  bottom: 0;
-  left: 0;
-  z-index: 2000;
-  height: 100%;
-  background-color: var(--el-overlay-color-lighter);
-  overflow: auto;
-}
-
 .cy-overlay-message-box {
   position: fixed;
   top: 0;

+ 30 - 32
src/components/js-dialog-comp/useDialogToJs.ts

@@ -1,7 +1,5 @@
 import {Component, h, ref} from 'vue';
 import {generateRandomString} from "@/utils/getUuid";
-import XEUtils from "xe-utils";
-import usePromise from "@/utils/cy-use/usePromise";
 
 export enum ClosingMethod {
     CONFIRM = 'confirm',
@@ -14,41 +12,41 @@ export const dialogEmits = {
 
 export const compList = ref<{
     [key: string]: {
+        onClosed(closingMethod: ClosingMethod, val: any): unknown;
         comp: any
     }
 }>({})
 
-/**
- * 使用这个函数的话不得下面的组件不可以有 onClosed 如果有需要换其他的名字不然这个会有问题
- * @param comp 组件
- * @param data props
- */
-function setDialogToJs<P extends Component>(comp: P, data: any) {
-    const [p, resolve, reject] = usePromise()
-
-    const id = "cy-dialog_js__" + generateRandomString(5)
-
-    function del() {
-        delete compList.value[id]
-    }
 
-    const props = {
-        onClosed: (closingMethod: ClosingMethod, val: any) => {
-            if (closingMethod === ClosingMethod.CONFIRM) {
-                resolve(val)
-            } else if (closingMethod === ClosingMethod.CANCEL) {
-                reject(val)
-            } else {
-                reject('使用该功能第一个参数需要为 ClosingMethod 枚举')
-                console.error('使用该功能第一个参数需要为 ClosingMethod 枚举')
-            }
-            del()
-        },
-        id,
-        ...data
-    }
-    compList.value[id] = {comp: h(comp, props)}
-    return p
+function setDialogToJs<P extends Component>(comp: P, data: any) {
+    return new Promise<any>((resolve, reject) => {
+        const id = "cy-dialog_js__" + generateRandomString(5)
+
+        function del() {
+            delete compList.value[id]
+        }
+
+        const props = {
+            onClosed: (closingMethod: ClosingMethod, val: any) => {
+                if (closingMethod === ClosingMethod.CONFIRM) {
+                    resolve(val)
+                } else if (closingMethod === ClosingMethod.CANCEL) {
+                    reject(val)
+                } else {
+                    reject('使用该功能第一个参数需要为 ClosingMethod 枚举')
+                    console.error('使用该功能第一个参数需要为 ClosingMethod 枚举')
+                }
+                del()
+            },
+            id,
+            ...data
+        }
+        compList.value[id] = {
+            comp: h(comp, props),
+            // @ts-ignore
+            onClosed: props.onClosed
+        }
+    })
 }
 
 export default setDialogToJs

+ 0 - 1
src/components/zhu-yuan-yi-sheng/yi-zhu-lu-ru/report-of-infectious-diseases/ReportOfInfectiousDiseases.vue

@@ -25,7 +25,6 @@ export declare type PropsType = {
   prompt?: string
 }
 
-
 const props = defineProps<PropsType>()
 const dialogRef = ref(null)
 

+ 3 - 0
src/components/zhu-yuan-yi-sheng/yi-zhu-lu-ru/yz-header/YzQueryCondition.vue

@@ -493,6 +493,8 @@ const crbOpen = () => {
   setDialogToJs(ReportOfInfectiousDiseases, {
     patNo: huanZheXinXi.value.inpatientNo,
     times: huanZheXinXi.value.admissTimes,
+  }).catch(() => {
+
   })
 }
 
@@ -521,6 +523,7 @@ const rcvrcalcost = async () => {
 }
 
 onMounted(async () => {
+
   yzMitt.on('queryYz', (val) => {
     return queryYz(val)
   })

+ 6 - 5
src/utils/blank-utils.ts → src/utils/blank-utils.js

@@ -1,4 +1,4 @@
-export function stringIsBlank(val: string) {
+export function stringIsBlank(val) {
     try {
         val = val.trim()
     } catch {
@@ -7,19 +7,20 @@ export function stringIsBlank(val: string) {
     return typeof val === 'undefined' || val === null || val === ''
 }
 
-export const stringNotBlank = (val: string) => !stringIsBlank(val)
+export const stringNotBlank = (val) => !stringIsBlank(val)
 
 
-export function listIsBlank(val: any[]) {
+export function listIsBlank(val) {
     return typeof val === 'undefined' || val === null || val.length === 0
 }
 
-export function listNotBlank(val: any[]) {
+export function listNotBlank(val) {
     return !listIsBlank(val)
 }
 
-export function listToStr(val: string[]) {
+export function listToStr(val) {
     if (listNotBlank(val)) {
+        val.join(",")
         return val.join(",") + "。";
     }
     return "";

+ 0 - 14
src/utils/cy-use/usePromise.ts

@@ -1,14 +0,0 @@
-// @ts-nocheck
-type UsePromiseReturn = [Promise<any>, (value?: any) => void, (error?: any) => void]
-
-export default function usePromise(): UsePromiseReturn {
-    let res: (value: any) => void = () => {
-    }
-    let rej: (error: any) => void = () => {
-    }
-    const p = new Promise((resolve, reject) => {
-        res = resolve
-        rej = reject
-    })
-    return [p, res, rej]
-}