Bläddra i källkod

引入无障碍组件

任一存 3 år sedan
förälder
incheckning
7312602d6f
31 ändrade filer med 1858 tillägg och 4 borttagningar
  1. 32 0
      web/README.md
  2. 442 0
      web/src/Help.vue
  3. 132 0
      web/src/assets/css/ariaGlobalStyle.less
  4. 2 0
      web/src/assets/css/common.less
  5. BIN
      web/src/assets/images/accessibility/big-cursor.cur
  6. BIN
      web/src/assets/images/accessibility/close_magnify_area.png
  7. BIN
      web/src/assets/images/accessibility/color_theme@2x.png
  8. BIN
      web/src/assets/images/accessibility/cross_cursor@2x.png
  9. BIN
      web/src/assets/images/accessibility/cursor_style@2x.png
  10. BIN
      web/src/assets/images/accessibility/elderly_severives_area_entry@2x.png
  11. BIN
      web/src/assets/images/accessibility/help@2x.png
  12. 41 0
      web/src/assets/images/accessibility/index.js
  13. BIN
      web/src/assets/images/accessibility/magnifier@2x.png
  14. BIN
      web/src/assets/images/accessibility/mute.png
  15. BIN
      web/src/assets/images/accessibility/mute@2x.png
  16. BIN
      web/src/assets/images/accessibility/mute_active@2x.png
  17. BIN
      web/src/assets/images/accessibility/quit@2x.png
  18. BIN
      web/src/assets/images/accessibility/reset@2x.png
  19. BIN
      web/src/assets/images/accessibility/screen_reader_area_entry@2x.png
  20. BIN
      web/src/assets/images/accessibility/screen_reader_mode@2x.png
  21. BIN
      web/src/assets/images/accessibility/screen_reader_mode_active@2x.png
  22. BIN
      web/src/assets/images/accessibility/shotcut@2x.png
  23. BIN
      web/src/assets/images/accessibility/speech_rate@2x.png
  24. BIN
      web/src/assets/images/accessibility/speech_rate_active@2x.png
  25. BIN
      web/src/assets/images/accessibility/zoom_in@2x.png
  26. BIN
      web/src/assets/images/accessibility/zoom_out@2x.png
  27. 8 0
      web/src/help.js
  28. 154 0
      web/src/utils.js
  29. 1014 0
      web/src/views/accessibility.vue
  30. 17 3
      web/src/views/layout/index.vue
  31. 16 1
      web/vue.config.js

+ 32 - 0
web/README.md

@@ -17,3 +17,35 @@ npm run build
 
 ### Customize configuration
 See [Configuration Reference](https://cli.vuejs.org/config/).
+
+# 无障碍相关
+
+## 特殊tabindex
+所有需要能通过tab键focus,并进一步朗读、在放大镜区域显示的元素(几乎是所有叶子元素)都需要添加tabindex attribute。tabindex具体取值是0还是-1还是正值,还需要依据产品设计而定。
+
+## 特殊class
+* aria-control-target: 手动添加。此节点和其后代会受无障碍菜单的控制。
+* aria-theme-inverse: 手动添加。此节点的背景文字颜色会和无障碍菜单里的设置相反。
+* aria-no-zoom: 手动添加。此节点不会受无障碍菜单里缩放的控制。
+* aria-hide: 手动添加。此节点不会显示,只是为了tab键focus。
+* aria-theme-[some color]: 自动添加。设置无障碍颜色时,这个class会被加到带有aria-control-target class的节点上。
+* aria-active: 自动添加。开启无障碍功能时添加到html和body上。
+* aria-magnifying: 自动添加。开启无障碍功能且开启其中的放大镜功能时添加到body上。
+
+## 特殊node(自动添加)
+* `<style id="aria-big-cursor-style-node"></style>`: 大号光标样式代码。
+* `<style id="aria-zoom-style-node"></style>`: 缩放样式代码。
+
+## 特殊id
+导航区需设置id: navigation-area
+视窗区需设置id: viewport-area
+交互区需设置id: interactive-area
+
+## 数据同步
+同一个域的多个页面的accessibility组件实例、该域下的唯一一份local storage之间的无障碍菜单设置会自动同步。
+
+初次加载一个组件实例,改变设置,自动保存到storage,触发storage事件,自动从storage读取设置,同步到组件,不会导致组件设置变化,over。
+
+第二次加载一个组件实例,起初为默认设置状态,自动从storage读取到设置,同步给该组件,因为该组件新设置状态与storage中相同,不会再同步回storage,over。
+
+在另一个页面改变组件实例设置状态,自动把该组件新的设置状态保存到storage,触发storage事件,自动把storage数据同步给当前所有加载了的组件,导致有些组件状态变化,但所有组件的新状态都和storage相同不会再同步回storege,over。

+ 442 - 0
web/src/Help.vue

@@ -0,0 +1,442 @@
+<template>
+  <div id="app">
+    <Accessibility />
+    <main class="aria-control-target">
+      <nav>
+        <h1>Catalogue</h1>
+        <section class="overview-wrapper">
+          <h2><a href="#Overview">Overview</a></h2>
+          <section class="overview-list">
+            <h3>
+              <a href="#What is the definition of website content accessibility?">
+                What is the definition of website content accessibility?
+              </a>
+            </h3>
+            <h3>
+              <a href="#What website accessibility features does this website provide?">
+                What website accessibility features does this website provide?
+              </a>
+            </h3>
+            <h3>
+              <a href="#What is included in the navigational functionality feature?">
+                What is included in the navigational functionality feature?
+              </a>
+            </h3>
+            <h3>
+              <a href="#What is included in the accessibility features for website browsing?">
+                What is included in the accessibility features for website browsing?
+              </a>
+            </h3>
+            <h3>
+              <a href="#How to use the accessibility toolbar when browsing the website?">
+                How to use the accessibility toolbar when browsing the website?
+              </a>
+            </h3>
+            <h3>
+              <a href="#What is the functionality of a cross cursor?">
+                What is the functionality of a cross cursor?
+              </a>
+            </h3>
+            <h3>
+              <a href="#How do visitors who are visually impaired or elderly browse this website?">
+                How do visitors who are visually impaired or elderly browse this website?
+              </a>
+            </h3>
+          </section>
+        </section>
+        <div class="splitter" />
+        <section>
+          <h2>
+            <a href="#Keyboard navigation">
+              Keyboard navigation
+            </a>
+          </h2>
+          <section>
+            <h3>
+              <a href="#Accessibility (Keyboard navigation)">
+                Accessibility (Keyboard navigation)
+              </a>
+            </h3>
+            <h3>
+              <a href="#Web navigation (Keyboard navigation)">
+                Web navigation (Keyboard navigation)
+              </a>
+            </h3>
+          </section>
+        </section>
+      </nav>
+      <article>
+        <h1>Accessibility Guidelines</h1>
+        <div class="splitter" />
+        <section>
+          <h2 id="Overview">
+            Overview
+          </h2>
+          <section>
+            <h3 id="What is the definition of website content accessibility?">
+              What is the definition of website content accessibility?
+            </h3>
+            <p>The term "website content accessibility" refers to no matter whether the non-disabled or the disabled, youth or elder can benefit from information technology, anyone can obtain and use website content in an equal, convenient, and barrier-free manner under any circumstances.</p>
+          </section>
+          <section>
+            <h3 id="What website accessibility features does this website provide?">
+              What website accessibility features does this website provide?
+            </h3>
+            <p>
+              This website mainly implements the following two accessibility features:
+            </p>
+            <ol>
+              <li>Improves navigational functionality</li>
+              <li>Accessibility feature for website browsing</li>
+            </ol>
+          </section>
+          <section>
+            <h3 id="What is included in the navigational functionality feature?">
+              What is included in the navigational functionality feature?
+            </h3>
+            <p>It fully supports mouse operation and keyboard navigation, making it easier to accommodate the usage preferences of certain groups.</p>
+          </section>
+          <section>
+            <h3 id="What is included in the accessibility features for website browsing?">
+              What is included in the accessibility features for website browsing?
+            </h3>
+            <ol>
+              <li>Accessibility features can be enabled or disabled for website browsing;</li>
+              <li>Interface can be reset;</li>
+              <li>Mouse pointer can be resized;</li>
+              <li>Cross cursor feature is supported;</li>
+              <li>Four types of interface colors can be altered;</li>
+              <li>Interface can be magnified and reduced;</li>
+              <li>Volume can be adjusted;</li>
+              <li>Screen reader is provided;</li>
+              <li>Textbox mode can be enabled;</li>
+            </ol>
+          </section>
+          <section>
+            <h3 id="How to use the accessibility toolbar when browsing the website?">
+              How to use the accessibility toolbar when browsing the website?
+            </h3>
+            <p>
+              This website provides the ability to enable and disable accessibility features. There is a "caring mode" button in the accessibility toolbar at the top of the website homepage.
+            </p>
+          </section>
+          <section>
+            <h3 id="What is the functionality of a cross cursor?">
+              What is the functionality of a cross cursor?
+            </h3>
+            <p>On this page, the cross cursor is represented by two red reference lines in a horizontal and vertical position, which assists visually challenged visitors in enhancing their reading experience.</p>
+          </section>
+          <section>
+            <h3 id="How do visitors who are visually impaired or elderly browse this website?">
+              How do visitors who are visually impaired or elderly browse this website?
+            </h3>
+            <p>This website has accessibility features for visually challenged and elderly users. The accessibility toolbar enables users to select the appropriate text size and interface color to promote the browsing experience of the webpage's content. Also, the cross-cursor feature allows locating the texts both horizontally and vertically. Additionally, users can select whether to receive content on the webpage via screen reader or not based on their own needs.</p>
+          </section>
+        </section>
+        <section>
+          <h2 id="Keyboard navigation">
+            Keyboard navigation
+          </h2>
+          <section>
+            <h3 id="Accessibility (Keyboard navigation)">
+              Accessibility (Keyboard navigation)
+            </h3>
+            <p>
+              All functionality of the content is operable through a keyboard interface without requiring specific timings for individual keystrokes.
+            </p>
+            <table>
+              <colgroup>
+                <col class="effect">
+                <col class="shortcut">
+                <col class="effect">
+                <col class="shortcut">
+              </colgroup>
+              <tr>
+                <th>Help:</th>
+                <td>(Shift + /)</td>
+                <th>Quit:</th>
+                <td>(Shift + Q)</td>
+              </tr>
+              <tr>
+                <th>Reset:</th>
+                <td>(Shift + 1)</td>
+                <th>Volume:</th>
+                <td>(Shift + 2)</td>
+              </tr>
+              <tr>
+                <th>Speech rate:</th>
+                <td>(Shift + 3)</td>
+                <th>Reading method:</th>
+                <td>(Shift + 4)</td>
+              </tr>
+              <tr>
+                <th>Color scheme:</th>
+                <td>(Shift + 5)</td>
+                <th>Zoom in:</th>
+                <td>(Shift + 6)</td>
+              </tr>
+              <tr>
+                <th>Zoom out:</th>
+                <td>(Shift + 7)</td>
+                <th>Cursor style:</th>
+                <td>(Shift + 8)</td>
+              </tr>
+              <tr>
+                <th>Cross cursor:</th>
+                <td>(Shift + 9)</td>
+                <th>Display mode:</th>
+                <td>(Shift + 0)</td>
+              </tr>
+              <tr>
+                <th>Shortcut:</th>
+                <td>(Shift + D)</td>
+                <th>Text-to-Speech:</th>
+                <td>(Shift + N)</td>
+              </tr>
+              <tr>
+                <th>Navigation:</th>
+                <td>(Alt + 1)</td>
+                <th>Viewport:</th>
+                <td>(Alt + 2)</td>
+              </tr>
+              <tr>
+                <th>Interactive port:</th>
+                <td>(Alt + 3)</td>
+                <th />
+                <td />
+              </tr>
+            </table>
+          </section>
+          <section>
+            <h3 id="Web navigation (Keyboard navigation)">
+              Web navigation (Keyboard navigation)
+            </h3>
+            <table>
+              <colgroup>
+                <col class="effect">
+                <col class="shortcut">
+                <col class="effect">
+                <col class="shortcut">
+              </colgroup>
+              <tr>
+                <th>Browsing:</th>
+                <td>(Tab)</td>
+                <th>Reverse:</th>
+                <td>(Shift + Tab)</td>
+              </tr>
+              <tr>
+                <th>Open the link:</th>
+                <td>(Enter)</td>
+                <th />
+                <td />
+              </tr>
+            </table>
+          </section>
+        </section>
+      </article>
+    </main>
+  </div>
+</template>
+
+<script>
+import Accessibility from '/src/views/accessibility.vue'
+
+export default {
+  name: 'App',
+  components: {
+    Accessibility,
+  }
+}
+</script>
+
+<style lang=less>
+html {
+  scroll-behavior: smooth;
+}
+body {
+  background: #F6F6F6;
+}
+main {
+  width: 1396px;
+  background: #fff;
+  margin-left: auto;
+  margin-right: auto;
+  padding: 57px 20px;
+  box-sizing: border-box;
+  > nav {
+    counter-reset: h2Counter;
+    display: inline-block;
+    width: 397px;
+    vertical-align: top;
+    margin-right: 16px;
+    border: 1px solid #DEDEDE;
+    box-sizing: border-box;
+    a {
+      color: inherit;
+      text-decoration: none;
+    }
+    > h1 {
+      height: 95px;
+      line-height: 95px;
+      box-sizing: border-box;
+      border-bottom: 1px solid #DEDEDE;
+      font-size: 36px;
+      font-family: Source Han Sans CN;
+      font-weight: 800;
+      color: #36584C;
+      text-align: center;
+    }
+    > section {
+      padding: 36px 22px;
+      > h2 {
+        position: relative;
+        margin-left: 1em;
+        font-size: 26px;
+        font-family: Source Han Sans CN;
+        font-weight: 800;
+        line-height: 22px;
+        color: #36584C;
+        margin-bottom: 21px;
+        counter-increment: h2Counter;
+        &:before {
+          content: counter(h2Counter) '.';
+          position: absolute;
+          margin-left: -1em;
+        }
+      }
+      > section {
+        counter-reset: h3Counter;
+        > h3 {
+          position: relative;
+          margin-left: 1em;
+          margin-bottom: 18px;
+          font-size: 20px;
+          font-family: SourceHanSansCN-Bold-GBpc-EUC-H;
+          line-height: 22px;
+          color: #000000;
+          &:last-child {
+            margin-bottom: 0;
+          }
+          counter-increment: h3Counter;
+          &:before {
+            content: counter(h3Counter) '.';
+            position: absolute;
+            margin-left: -1em;
+          }
+        }
+      }
+    }
+    > .splitter {
+      margin: 0 27px;
+      height: 1px;
+      background: #36584C;
+      opacity: 0.3;
+    }
+  }
+  > article {
+    display: inline-block;
+    width: 942px;
+    vertical-align: top;
+    border: 1px solid #DEDEDE;
+    box-sizing: border-box;
+    counter-reset: h2Counter;
+    > h1 {
+      height: 95px;
+      line-height: 95px;
+      box-sizing: border-box;
+      font-size: 36px;
+      font-family: Source Han Sans CN;
+      font-weight: 800;
+      color: #000000;
+      text-align: center;
+    }
+    > .splitter {
+      margin: 0 27px;
+      border-bottom: 1px dashed #36584C;
+      opacity: 0.3;
+    }
+    > section {
+      counter-reset: h3Counter;
+      margin: 35px 50px 35px 50px;
+      > h2 {
+        position: relative;
+        margin-left: 1em;
+        font-size: 26px;
+        font-family: Source Han Sans CN;
+        font-weight: 800;
+        line-height: 22px;
+        color: #000;
+        margin-bottom: 30px;
+        counter-increment: h2Counter;
+        &:before {
+          content: counter(h2Counter) '.';
+          left: -1em;
+          position: absolute;
+        }
+      }
+      > section {
+        margin-bottom: 25px;
+        > h3 {
+          position: relative;
+          margin-left: 1em;
+          margin-bottom: 18px;
+          font-size: 20px;
+          font-family: SourceHanSansCN-Bold-GBpc-EUC-H;
+          font-weight: 600;
+          line-height: 22px;
+          color: #000000;
+          &:last-child {
+            margin-bottom: 0;
+          }
+          counter-increment: h3Counter;
+          &:before {
+            content: counter(h3Counter) '.';
+            position: absolute;
+            margin-left: -1em;
+          }
+        }
+        > p {
+          font-size: 20px;
+        }
+        > ol {
+          counter-reset: h4Counter;
+          > li {
+            counter-increment: h4Counter;
+            font-size: 20px;
+            list-style: none;
+            margin-left: 1.2em;
+            margin-top: 1em;
+            &:before {
+              content: counter(h4Counter) ')';
+              position: absolute;
+              margin-left: -1.2em;
+            }
+          }
+        }
+        > table {
+          border: 1px solid black;
+          width: 100%;
+          font-size: 20px;
+          margin-top: 18px;
+          > colgroup {
+            > col.effect {
+              background: #efefef;
+            }
+            > col.shortcut {
+              background: #f9f9f9;
+            }
+          }
+          > tr {
+            > th, td {
+              border: 1px solid black;
+              text-align: center;
+              width: 25%;
+              padding: 0.5em 0;
+            }
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 132 - 0
web/src/assets/css/ariaGlobalStyle.less

@@ -0,0 +1,132 @@
+@import './common.less';
+
+.aria-hide {
+  display: none !important;
+}
+
+.aria-theme-default {
+  &:focus {
+    outline: 3px solid red;
+  }
+  * {
+    &:focus {
+      outline: 3px solid red;
+    }
+  }
+}
+
+.aria-theme-white {
+  background-color: white !important;
+  color: black !important;
+  &:focus {
+    outline: 3px solid black;
+  }
+  * { // TODO: 看看博物馆项目代码有没有精细地只给需要变色的元素添加class。
+    background-color: white !important;
+    color: black !important;
+    &:focus {
+      outline: 3px solid black;
+    }
+  }
+  a {
+    color: blue !important;
+  }
+  &.aria-inverse-theme {
+    background-color: black !important;
+    color: white !important;
+  }
+  .aria-inverse-theme {
+    background-color: black !important;
+    color: white !important;
+  }
+}
+
+.aria-theme-blue {
+  background-color: blue !important;
+  color: yellow !important;
+  &:focus {
+    outline: 3px solid blue;
+  }
+  * {
+    background-color: blue !important;
+    color: yellow !important;
+    &:focus {
+      outline: 3px solid blue;
+    }
+  }
+  a {
+    color: white !important;
+  }
+  &.aria-inverse-theme {
+    background-color: yellow !important;
+    color: blue !important;
+  }
+  .aria-inverse-theme {
+    background-color: yellow !important;
+    color: blue !important;
+  }
+}
+
+.aria-theme-yellow {
+  background-color: yellow !important;
+  color: black !important;
+  &:focus {
+    outline: 3px solid yellow;
+  }
+  * {
+    background-color: yellow !important;
+    color: black !important;
+    &:focus {
+      outline: 3px solid yellow;
+    }
+  }
+  a {
+    color: blue !important;
+  }
+  &.aria-inverse-theme {
+    background-color: black !important;
+    color: yellow !important;
+  }
+  .aria-inverse-theme {
+    background-color: black !important;
+    color: yellow !important;
+  }
+}
+
+.aria-theme-black {
+  background-color: black !important;
+  color: yellow !important;
+  &:focus {
+    outline: 3px solid black;
+  }
+  * {
+    background-color: black !important;
+    color: yellow !important;
+    &:focus {
+      outline: 3px solid black;
+    }
+  }
+  a {
+    color: white !important;
+  }
+  &.aria-inverse-theme {
+    background-color: yellow !important;
+    color: black !important;
+  }
+  .aria-inverse-theme {
+    background-color: yellow !important;
+    color: black !important;
+  }
+}
+
+body.aria-active {
+  margin-top: @accessibility-menu-height;
+}
+
+body.aria-active.aria-magnifying {
+  margin-bottom: @magnify-area-height;
+}
+
+html.aria-active {
+  scroll-padding-top: @accessibility-menu-height;
+}

+ 2 - 0
web/src/assets/css/common.less

@@ -0,0 +1,2 @@
+@accessibility-menu-height: 105px;
+@magnify-area-height: 219px;

BIN
web/src/assets/images/accessibility/big-cursor.cur


BIN
web/src/assets/images/accessibility/close_magnify_area.png


BIN
web/src/assets/images/accessibility/color_theme@2x.png


BIN
web/src/assets/images/accessibility/cross_cursor@2x.png


BIN
web/src/assets/images/accessibility/cursor_style@2x.png


BIN
web/src/assets/images/accessibility/elderly_severives_area_entry@2x.png


BIN
web/src/assets/images/accessibility/help@2x.png


+ 41 - 0
web/src/assets/images/accessibility/index.js

@@ -0,0 +1,41 @@
+import reset from "./reset@2x.png"
+import mute from './mute@2x.png'
+import muteActive from './mute_active@2x.png'
+import speechRate from "./speech_rate@2x.png"
+import speechRateActive from "./speech_rate_active@2x.png"
+import screenReaderMode from "./screen_reader_mode@2x.png"
+import screenReaderModeActive from "./screen_reader_mode_active@2x.png"
+import colorTheme from "./color_theme@2x.png"
+import zoomIn from "./zoom_in@2x.png"
+import zoomOut from "./zoom_out@2x.png"
+import cursorStyle from "./cursor_style@2x.png"
+import crossCursor from "./cross_cursor@2x.png"
+import magnifier from "./magnifier@2x.png"
+import help from "./help@2x.png"
+import shotcut from "./shotcut@2x.png"
+import quit from "./quit@2x.png"
+import screenReaderAreaEntry from "./screen_reader_area_entry@2x.png"
+import elderlyServicesAreaEntry from './elderly_severives_area_entry@2x.png'
+import closeMagnifyArea from "./close_magnify_area.png"
+
+export default {
+  reset,
+  mute,
+  muteActive,
+  speechRate,
+  speechRateActive,
+  screenReaderMode,
+  screenReaderModeActive,
+  colorTheme,
+  zoomIn,
+  zoomOut,
+  cursorStyle,
+  crossCursor,
+  magnifier,
+  help,
+  shotcut,
+  quit,
+  screenReaderAreaEntry,
+  elderlyServicesAreaEntry,
+  closeMagnifyArea,
+}

BIN
web/src/assets/images/accessibility/magnifier@2x.png


BIN
web/src/assets/images/accessibility/mute.png


BIN
web/src/assets/images/accessibility/mute@2x.png


BIN
web/src/assets/images/accessibility/mute_active@2x.png


BIN
web/src/assets/images/accessibility/quit@2x.png


BIN
web/src/assets/images/accessibility/reset@2x.png


BIN
web/src/assets/images/accessibility/screen_reader_area_entry@2x.png


BIN
web/src/assets/images/accessibility/screen_reader_mode@2x.png


BIN
web/src/assets/images/accessibility/screen_reader_mode_active@2x.png


BIN
web/src/assets/images/accessibility/shotcut@2x.png


BIN
web/src/assets/images/accessibility/speech_rate@2x.png


BIN
web/src/assets/images/accessibility/speech_rate_active@2x.png


BIN
web/src/assets/images/accessibility/zoom_in@2x.png


BIN
web/src/assets/images/accessibility/zoom_out@2x.png


+ 8 - 0
web/src/help.js

@@ -0,0 +1,8 @@
+import Vue from 'vue'
+import Help from './Help.vue'
+
+Vue.config.productionTip = false
+
+new Vue({
+  render: h => h(Help),
+}).$mount('#app')

+ 154 - 0
web/src/utils.js

@@ -0,0 +1,154 @@
+function mapTags(tag) {
+  let ret = ''
+  switch (tag) {
+  case 'A':
+    ret = 'link: '
+    break
+  case 'IMG':
+    ret = 'image: '
+    break
+  case 'BUTTON':
+    ret = 'button: '
+    break
+  default:
+    break
+  }
+  return ret
+}
+
+function extractTextForMagnify(e) {
+  // console.log(e)
+  if (e.path[0].nodeName === 'HTML') {
+    return
+  }
+  // console.log(e.path[0].tagName)
+  if (e.path[0].children.length === 0) {
+    return {
+      elemDisc: mapTags(e.path[0].tagName),
+      elemContent: e.path[0].innerText,
+    }
+  } else {
+    const eventTargetChildNodes = Array(...e.path[0].childNodes)
+    if (eventTargetChildNodes.some(item => (item.nodeType === 3) || (item.nodeType === 1))) {
+      return {
+        elemDisc: mapTags(e.path[0].tagName),
+        elemContent: e.path[0].innerText,
+      }
+    }
+  }
+}
+
+function isObject(p) {
+  return Object.prototype.toString.call(p) === '[object Object]'
+}
+
+// 判断两个对象内容是否相同
+function isSameObject(object1, object2) {
+  const keys1 = Object.keys(object1)
+  const keys2 = Object.keys(object2)
+
+  if (keys1.length !== keys2.length) {
+    return false
+  }
+
+  for (let index = 0; index < keys1.length; index++) {
+    const val1 = object1[keys1[index]]
+    const val2 = object2[keys2[index]]
+    const areObjects = isObject(val1) && isObject(val2)
+    if (
+      (areObjects && !isSameObject(val1, val2)) ||
+      (!areObjects && (val1 !== val2))
+    ) {
+      return false
+    }
+  }
+  return true
+}
+
+function focusNextFocusableNode(treeWalker) {
+  // eslint-disable-next-line
+  while(true) {
+    const nextNode = treeWalker.nextNode()
+    if (!nextNode) {
+      return false
+    }
+    if (nextNode.focus) {
+      nextNode.focus()
+      if (document.activeElement === nextNode) {
+        return true
+      }
+    }
+  }
+}
+
+function iterateOnFocusableNode(startNode, focusedNodeHandler) {
+  const treeWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_ELEMENT)
+  treeWalker.currentNode = startNode
+  treeWalker.currentNode.focus()
+  if (document.activeElement === treeWalker.currentNode) {
+    // console.log('起始节点可以focus')
+  } else {
+    // console.log('起始节点不可以focus,focus到下一节点。')
+    const ret = focusNextFocusableNode(treeWalker)
+    if (!ret) {
+      return
+    }
+  }
+
+  const iterator = () => {
+    focusedNodeHandler(treeWalker.currentNode).then(() => {
+      const result = focusNextFocusableNode(treeWalker)
+      if (result) {
+        // console.log('遍历到下一个节点!')
+        iterator()
+      } else {
+        // console.log('遍历结束!')
+      }
+    }).catch((e) => {
+      // console.log('遍历中止!', e)
+    })
+  }
+  iterator()
+}
+
+/**
+ * 返回一个自带消抖效果的函数,用res表示。
+ *
+ * fn: 需要被消抖的函数
+ * delay: 消抖时长
+ * isImmediateCall: 是在第一次调用时立即执行fn,还是在最后一次调用后等delay时长再调用fn
+ */
+function debounce(fn, delay, isImmediateCall = false) {
+  let timer = null
+  // 上次调用的时刻
+  let lastCallTime = 0
+
+  if (isImmediateCall) {
+    return function (...args) {
+      const context = this
+      const currentTime = Date.now()
+      if (currentTime - lastCallTime >= delay) {
+        fn.apply(context, args)
+      }
+      lastCallTime = currentTime
+    }
+  } else {
+    return function (...args) {
+      if (timer) {
+        clearTimeout(timer)
+      }
+      const context = this
+      timer = setTimeout(() => {
+        fn.apply(context, args)
+      }, delay)
+    }
+  }
+}
+
+export default {
+  mapTags,
+  extractTextForMagnify,
+  isSameObject,
+  iterateOnFocusableNode,
+  debounce,
+}

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 1014 - 0
web/src/views/accessibility.vue


+ 17 - 3
web/src/views/layout/index.vue

@@ -1,5 +1,6 @@
 <template>
   <div class="Layout">
+    <Accessibility ref="accessibility" @hide="onAccessibilityMenuHide"/>
     <!-- 公共头部 -->
     <div class="topNav">
       <div class="mainbav_wrap">
@@ -32,7 +33,7 @@
           <!-- 爱心模式 -->
           <li class="love">
             <span>Caring Mode</span>&nbsp;
-            <el-switch v-model="loveFalg" active-color="#AB3434"> </el-switch>
+            <el-switch v-model="loveFlag" active-color="#AB3434"> </el-switch>
           </li>
         </ul>
       </div>
@@ -83,15 +84,19 @@
 </template>
 
 <script>
+import Accessibility from '/src/views/accessibility.vue'
 import { topData } from "./data";
+
 export default {
   name: "Layout",
-  components: {},
+  components: {
+    Accessibility,
+  },
   data() {
     //这里存放数据
     return {
       topData,
-      loveFalg: false,
+      loveFlag: false,
       searchTxt: "",
       footerData: [
         { name: "|&emsp; Site Index", path: "/Layout/Index" },
@@ -112,9 +117,18 @@ export default {
       this.searchTxt=''
       // console.log('------',this.menaInd);
     },
+    loveFlag: {
+      handler(v) {
+        this.$refs.accessibility.ariaSettings.isCompActive = v
+      },
+      immediate: false,
+    },
   },
   //方法集合
   methods: {
+    onAccessibilityMenuHide() {
+      this.loveFlag = false
+    },
     // 点击搜索
     search() {
       // if (this.searchTxt.trim() === "" || this.searchTxt.trim().length < 4) return

+ 16 - 1
web/vue.config.js

@@ -1,3 +1,18 @@
 module.exports = {
-  publicPath: './'
+  pages: {
+    main: 'src/main.js',
+    help: 'src/help.js',
+  },
+  configureWebpack: {
+    module: {
+      rules: [
+        {
+          test: /\.cur$/,
+          use: {
+            loader: 'file-loader'
+          }
+        }
+      ]
+    }
+  }
 }