diff --git a/public/LOSEHU117P6.bin b/public/LOSEHU117P6.bin
new file mode 100644
index 0000000..080fd36
Binary files /dev/null and b/public/LOSEHU117P6.bin differ
diff --git a/public/LOSEHU117P6K.bin b/public/LOSEHU117P6K.bin
new file mode 100644
index 0000000..f8ecd14
Binary files /dev/null and b/public/LOSEHU117P6K.bin differ
diff --git a/src/router/routes/modules/guide.ts b/src/router/routes/modules/guide.ts
new file mode 100644
index 0000000..48f30d3
--- /dev/null
+++ b/src/router/routes/modules/guide.ts
@@ -0,0 +1,28 @@
+import { DEFAULT_LAYOUT } from '../base';
+import { AppRouteRecordRaw } from '../types';
+
+const GUIDE: AppRouteRecordRaw = {
+  path: '/guide',
+  name: 'guide',
+  component: DEFAULT_LAYOUT,
+  meta: {
+    locale: '指南',
+    requiresAuth: true,
+    icon: 'icon-list',
+    order: 3,
+  },
+  children: [
+    {
+      path: 'f117',
+      name: 'f117',
+      component: () => import('@/views/guide/f117/index.vue'),
+      meta: {
+        locale: '使用117P6版',
+        requiresAuth: true,
+        roles: ['*'],
+      },
+    },
+  ],
+};
+
+export default GUIDE;
diff --git a/src/utils/serial.js b/src/utils/serial.js
index b4020b8..bd3d027 100644
--- a/src/utils/serial.js
+++ b/src/utils/serial.js
@@ -1099,6 +1099,143 @@ function getFontByte(string){
     return [parseInt(last_byte), parseInt(byte)]
 }
 
+function flash_generateCommand(data, address, totalSize) {
+    // the flash command structure is as follows:
+    /* 0x19  0x5  0xc  0x1  0x8a  0x8d  0x9f  0x1d  
+     * address_msb  address_lsb  address_final_msb  address_final_lsb  length_msb  length_lsb  0x0  0x0 
+     * [0x100 bytes of data, if length is <0x100 then fill the rest with zeroes] */
+
+    // flash is written in 0x100 blocks, if data is less than 0x100 bytes then it is padded with zeroes
+    if (data.length < 0x100) {
+        const padding = new Uint8Array(0x100 - data.length);
+        data = new Uint8Array([...data, ...padding]);
+    }
+    if (data.length != 0x100) throw new Error('Tell matt that he is an idiot');
+
+    // the address is a 16 bit integer, so we need to split it into two bytes
+    const address_msb = (address & 0xff00) >> 8;
+    const address_lsb = address & 0xff;
+
+    const address_final = (totalSize + 0xff) & ~0xff; // add 0xff to totalSize and then round down to the next multiple of 0x100 by stripping the last byte
+    if (address_final > 0xf000) throw new Error('Total size is too large');
+    const address_final_msb = (address_final & 0xff00) >> 8;
+    const address_final_lsb = 0x0; // since address_final can only be a multiple of 0x100, address_final_lsb is always 0x0
+
+    // the length is fixed to 0x100 bytes
+    const length_msb = 0x01;
+    const length_lsb = 0x00;
+
+    return new Uint8Array([0x19, 0x5, 0xc, 0x1, 0x8a, 0x8d, 0x9f, 0x1d, address_msb, address_lsb, address_final_msb, address_final_lsb, length_msb, length_lsb, 0x0, 0x0, ...data]);
+}
+
+async function flash_flashFirmware(port, firmware) {
+    // for loop to flash the firmware in 0x100 byte blocks
+    // this loop is safe as long as the firmware file is smaller than 0xf000 bytes
+    if (firmware.length > 0xefff) throw new Error('Last resort boundary check failed. Whoever touched the code is an idiot.');
+    console.log('Flashing... 0%')
+
+    for (let i = 0; i < firmware.length; i += 0x100) {
+        const data = firmware.slice(i, i + 0x100);
+        const command = flash_generateCommand(data, i, firmware.length);
+
+        try {
+            await sendPacket(port, command);
+            await readPacket(port, 0x1a);
+        } catch (e) {
+            console.log('Flash command rejected. Aborting.');
+            return Promise.reject(e);
+        }
+
+        console.log(`Flashing... ${((i / firmware.length) * 100).toFixed(1)}%`, true);
+    }
+    console.log('Flashing... 100%', true)
+    console.log('Successfully flashed firmware.');
+    return Promise.resolve();
+}
+
+const Crc16Tab = [0, 4129, 8258, 12387, 16516, 20645, 24774, 28903, 33032, 37161, 41290, 45419, 49548, 53677, 57806, 61935, 4657, 528, 12915, 8786, 21173, 17044, 29431, 25302, 37689, 33560, 45947, 41818, 54205, 50076, 62463, 58334, 9314, 13379, 1056, 5121, 25830, 29895, 17572, 21637, 42346, 46411, 34088, 38153, 58862, 62927, 50604, 54669, 13907, 9842, 5649, 1584, 30423, 26358, 22165, 18100, 46939, 42874, 38681, 34616, 63455, 59390, 55197, 51132, 18628, 22757, 26758, 30887, 2112, 6241, 10242, 14371, 51660, 55789, 59790, 63919, 35144, 39273, 43274, 47403, 23285, 19156, 31415, 27286, 6769, 2640, 14899, 10770, 56317, 52188, 64447, 60318, 39801, 35672, 47931, 43802, 27814, 31879, 19684, 23749, 11298, 15363, 3168, 7233, 60846, 64911, 52716, 56781, 44330, 48395, 36200, 40265, 32407, 28342, 24277, 20212, 15891, 11826, 7761, 3696, 65439, 61374, 57309, 53244, 48923, 44858, 40793, 36728, 37256, 33193, 45514, 41451, 53516, 49453, 61774, 57711, 4224, 161, 12482, 8419, 20484, 16421, 28742, 24679, 33721, 37784, 41979, 46042, 49981, 54044, 58239, 62302, 689, 4752, 8947, 13010, 16949, 21012, 25207, 29270, 46570, 42443, 38312, 34185, 62830, 58703, 54572, 50445, 13538, 9411, 5280, 1153, 29798, 25671, 21540, 17413, 42971, 47098, 34713, 38840, 59231, 63358, 50973, 55100, 9939, 14066, 1681, 5808, 26199, 30326, 17941, 22068, 55628, 51565, 63758, 59695, 39368, 35305, 47498, 43435, 22596, 18533, 30726, 26663, 6336, 2273, 14466, 10403, 52093, 56156, 60223, 64286, 35833, 39896, 43963, 48026, 19061, 23124, 27191, 31254, 2801, 6864, 10931, 14994, 64814, 60687, 56684, 52557, 48554, 44427, 40424, 36297, 31782, 27655, 23652, 19525, 15522, 11395, 7392, 3265, 61215, 65342, 53085, 57212, 44955, 49082, 36825, 40952, 28183, 32310, 20053, 24180, 11923, 16050, 3793, 7920];
+
+function crc16_ccitt(data) {
+    var i2, out;
+    i2 = 0;
+
+    for (var i3 = 0, _pj_a = data.length; i3 < _pj_a; i3 += 1) {
+        out = Crc16Tab[(i2 >> 8 ^ data[i3]) & 255];
+        i2 = out ^ i2 << 8;
+    }
+
+    return 65535 & i2;
+}
+
+function crc16_ccitt_le(data) {
+    var crc;
+    crc = crc16_ccitt(data);
+    return new Uint8Array([crc & 255, crc >> 8]);
+}
+
+function firmware_xor(fwcontent) {
+    const XOR_ARRAY = new Uint8Array([
+        0x47, 0x22, 0xc0, 0x52, 0x5d, 0x57, 0x48, 0x94, 0xb1, 0x60, 0x60, 0xdb, 0x6f, 0xe3, 0x4c, 0x7c,
+        0xd8, 0x4a, 0xd6, 0x8b, 0x30, 0xec, 0x25, 0xe0, 0x4c, 0xd9, 0x00, 0x7f, 0xbf, 0xe3, 0x54, 0x05,
+        0xe9, 0x3a, 0x97, 0x6b, 0xb0, 0x6e, 0x0c, 0xfb, 0xb1, 0x1a, 0xe2, 0xc9, 0xc1, 0x56, 0x47, 0xe9,
+        0xba, 0xf1, 0x42, 0xb6, 0x67, 0x5f, 0x0f, 0x96, 0xf7, 0xc9, 0x3c, 0x84, 0x1b, 0x26, 0xe1, 0x4e,
+        0x3b, 0x6f, 0x66, 0xe6, 0xa0, 0x6a, 0xb0, 0xbf, 0xc6, 0xa5, 0x70, 0x3a, 0xba, 0x18, 0x9e, 0x27,
+        0x1a, 0x53, 0x5b, 0x71, 0xb1, 0x94, 0x1e, 0x18, 0xf2, 0xd6, 0x81, 0x02, 0x22, 0xfd, 0x5a, 0x28,
+        0x91, 0xdb, 0xba, 0x5d, 0x64, 0xc6, 0xfe, 0x86, 0x83, 0x9c, 0x50, 0x1c, 0x73, 0x03, 0x11, 0xd6,
+        0xaf, 0x30, 0xf4, 0x2c, 0x77, 0xb2, 0x7d, 0xbb, 0x3f, 0x29, 0x28, 0x57, 0x22, 0xd6, 0x92, 0x8b
+    ]);
+    const XOR_LEN = XOR_ARRAY.length;
+
+    for (let i = 0; i < fwcontent.length; i += 1) {
+        fwcontent[i] ^= XOR_ARRAY[i % XOR_LEN];
+    }
+
+    return fwcontent;
+}
+
+
+function unpack(encoded_firmware) {
+
+    if (crc16_ccitt_le(encoded_firmware.slice(0, -2)).toString() === encoded_firmware.slice(-2).toString()) {
+        console.log("CRC check passed...");
+    } else {
+        console.log("WARNING: CRC CHECK FAILED! FIRMWARE NOT VALID!\nMake sure to choose a flashable bin file. ");
+    }
+
+    const decoded_firmware = firmware_xor(encoded_firmware.slice(0, -2));
+    const versionInfoOffset = 0x2000;
+    const versionInfoLength = 16;
+    const resultLength = decoded_firmware.length - versionInfoLength;
+    const result = new Uint8Array(resultLength);
+
+    result.set(decoded_firmware.subarray(0, versionInfoOffset));
+    result.set(decoded_firmware.subarray(versionInfoOffset + versionInfoLength), versionInfoOffset);
+
+    return result;
+}
+
+function unpackVersion(encoded_firmware) {
+
+    if (crc16_ccitt_le(encoded_firmware.slice(0, -2)).toString() === encoded_firmware.slice(-2).toString()) {
+        console.log("CRC check passed...");
+    } else {
+        console.log("WARNING: CRC CHECK FAILED! FIRMWARE NOT VALID!\nMake sure to choose a flashable bin file. ");
+    }
+
+    const decoded_firmware = firmware_xor(encoded_firmware.slice(0, -2));
+    const versionInfoOffset = 0x2000;
+    const versionInfoLength = 16;
+    const resultLength = decoded_firmware.length - versionInfoLength;
+    const result = new Uint8Array(resultLength);
+
+    result.set(decoded_firmware.subarray(0, versionInfoOffset));
+    result.set(decoded_firmware.subarray(versionInfoOffset + versionInfoLength), versionInfoOffset);
+
+    const rawVersion = decoded_firmware.subarray(versionInfoOffset, versionInfoOffset + versionInfoLength);
+
+    return rawVersion;
+}
+
 export {
     connect,
     disconnect,
@@ -1113,5 +1250,8 @@ export {
     eeprom_read,
     eeprom_reboot,
     check_eeprom,
-    eeprom_write
+    eeprom_write,
+    flash_flashFirmware,
+    unpackVersion,
+    unpack
 }
\ No newline at end of file
diff --git a/src/views/guide/f117/assets/cj1.png b/src/views/guide/f117/assets/cj1.png
new file mode 100644
index 0000000..390c1f3
Binary files /dev/null and b/src/views/guide/f117/assets/cj1.png differ
diff --git a/src/views/guide/f117/assets/cj2.png b/src/views/guide/f117/assets/cj2.png
new file mode 100644
index 0000000..8937621
Binary files /dev/null and b/src/views/guide/f117/assets/cj2.png differ
diff --git a/src/views/guide/f117/assets/cj3.png b/src/views/guide/f117/assets/cj3.png
new file mode 100644
index 0000000..ffeced6
Binary files /dev/null and b/src/views/guide/f117/assets/cj3.png differ
diff --git a/src/views/guide/f117/components/card-wrap.vue b/src/views/guide/f117/components/card-wrap.vue
new file mode 100644
index 0000000..9b876d8
--- /dev/null
+++ b/src/views/guide/f117/components/card-wrap.vue
@@ -0,0 +1,204 @@
+<template>
+  <div class="card-wrap">
+    <a-card v-if="loading" :bordered="false" hoverable>
+      <slot name="skeleton"></slot>
+    </a-card>
+    <a-card v-else :bordered="false" hoverable>
+      <a-space align="start">
+        <a-avatar
+          v-if="icon"
+          :size="24"
+          style="margin-right: 8px; background-color: #626aea"
+        >
+          <icon-filter />
+        </a-avatar>
+        <a-card-meta>
+          <template #title>
+            <a-typography-text style="margin-right: 10px">
+              {{ title }}
+            </a-typography-text>
+            <template v-if="showTag">
+              <a-tag
+                v-if="open && isExpires === false"
+                size="small"
+                color="green"
+              >
+                <template #icon>
+                  <icon-check-circle-fill />
+                </template>
+                <span>{{ tagText }}</span>
+              </a-tag>
+              <a-tag v-else-if="isExpires" size="small" color="red">
+                <template #icon>
+                  <icon-check-circle-fill />
+                </template>
+                <span>{{ expiresTagText }}</span>
+              </a-tag>
+            </template>
+          </template>
+          <template #description>
+            {{ description }}
+            <slot></slot>
+          </template>
+        </a-card-meta>
+      </a-space>
+      <template #actions>
+        <a-switch v-if="actionType === 'switch'" v-model="open" />
+        <a-space v-else-if="actionType === 'button'">
+          <template v-if="isExpires">
+            <a-button type="outline" @click="renew">
+              {{ expiresText }}
+            </a-button>
+          </template>
+          <template v-else>
+            <a-button v-if="open" @click="handleToggle">
+              {{ closeTxt }}
+            </a-button>
+            <a-button v-else-if="!open" type="outline" @click="handleToggle">
+              {{ openTxt }}
+            </a-button>
+          </template>
+        </a-space>
+        <div v-else>
+          <a-space>
+            <a-button @click="toggle(false)">
+              {{ closeTxt }}
+            </a-button>
+            <a-button type="primary" @click="toggle(true)">
+              {{ openTxt }}
+            </a-button>
+          </a-space>
+        </div>
+      </template>
+    </a-card>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { ref } from 'vue';
+  import { useToggle } from '@vueuse/core';
+
+  const props = defineProps({
+    loading: {
+      type: Boolean,
+      default: false,
+    },
+    title: {
+      type: String,
+      default: '',
+    },
+    description: {
+      type: String,
+      default: '',
+    },
+    actionType: {
+      type: String,
+      default: '',
+    },
+    defaultValue: {
+      type: Boolean,
+      default: false,
+    },
+    openTxt: {
+      type: String,
+      default: '',
+    },
+    closeTxt: {
+      type: String,
+      default: '',
+    },
+    expiresText: {
+      type: String,
+      default: '',
+    },
+    icon: {
+      type: String,
+      default: '',
+    },
+    showTag: {
+      type: Boolean,
+      default: true,
+    },
+    tagText: {
+      type: String,
+      default: '',
+    },
+    expires: {
+      type: Boolean,
+      default: false,
+    },
+    expiresTagText: {
+      type: String,
+      default: '',
+    },
+  });
+  const [open, toggle] = useToggle(props.defaultValue);
+  const handleToggle = () => {
+    toggle();
+  };
+  const isExpires = ref(props.expires);
+  const renew = () => {
+    isExpires.value = false;
+  };
+</script>
+
+<style scoped lang="less">
+  .card-wrap {
+    height: 100%;
+    transition: all 0.3s;
+    border: 1px solid var(--color-neutral-3);
+    border-radius: 4px;
+    &:hover {
+      transform: translateY(-4px);
+      // box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.1);
+    }
+    :deep(.arco-card) {
+      height: 100%;
+      border-radius: 4px;
+      .arco-card-body {
+        height: 100%;
+        .arco-space {
+          width: 100%;
+          height: 100%;
+          .arco-space-item {
+            height: 100%;
+            &:last-child {
+              flex: 1;
+            }
+            .arco-card-meta {
+              height: 100%;
+              display: flex;
+              flex-flow: column;
+              .arco-card-meta-content {
+                flex: 1;
+                .arco-card-meta-description {
+                  margin-top: 8px;
+                  color: rgb(var(--gray-6));
+                  line-height: 20px;
+                  font-size: 12px;
+                }
+              }
+              .arco-card-meta-footer {
+                margin-top: 0;
+              }
+            }
+          }
+        }
+      }
+    }
+    :deep(.arco-card-meta-title) {
+      display: flex;
+      align-items: center;
+
+      // To prevent the shaking
+      line-height: 28px;
+    }
+    :deep(.arco-skeleton-line) {
+      &:last-child {
+        display: flex;
+        justify-content: flex-end;
+        margin-top: 20px;
+      }
+    }
+  }
+</style>
diff --git a/src/views/guide/f117/components/quality-inspection.vue b/src/views/guide/f117/components/quality-inspection.vue
new file mode 100644
index 0000000..88167b6
--- /dev/null
+++ b/src/views/guide/f117/components/quality-inspection.vue
@@ -0,0 +1,115 @@
+<template>
+  <div class="list-wrap">
+    <a-typography-title class="block-title" :heading="6">
+      {{ $t('cardList.tab.title.content') }}
+    </a-typography-title>
+    <a-row class="list-row" :gutter="24">
+      <a-col
+        :xs="12"
+        :sm="12"
+        :md="12"
+        :lg="6"
+        :xl="6"
+        :xxl="6"
+        class="list-col"
+      >
+        <div class="card-wrap empty-wrap">
+          <a-card :bordered="false" hoverable>
+            <a-result :status="null" :title="$t('cardList.content.action')">
+              <template #icon>
+                <icon-plus style="font-size: 20px" />
+              </template>
+            </a-result>
+          </a-card>
+        </div>
+      </a-col>
+      <a-col
+        v-for="item in renderData"
+        :key="item.id"
+        class="list-col"
+        :xs="12"
+        :sm="12"
+        :md="12"
+        :lg="6"
+        :xl="6"
+        :xxl="6"
+      >
+        <CardWrap
+          :loading="loading"
+          :title="item.title"
+          :description="item.description"
+          :default-value="item.enable"
+          :action-type="item.actionType"
+          :icon="item.icon"
+          :open-txt="$t('cardList.content.inspection')"
+          :close-txt="$t('cardList.content.delete')"
+          :show-tag="false"
+        >
+          <a-descriptions
+            style="margin-top: 16px"
+            :data="item.data"
+            layout="inline-horizontal"
+            :column="2"
+          />
+          <template #skeleton>
+            <a-skeleton :animation="true">
+              <a-skeleton-line
+                :widths="['50%', '50%', '100%', '40%']"
+                :rows="4"
+              />
+              <a-skeleton-line :widths="['40%']" :rows="1" />
+            </a-skeleton>
+          </template>
+        </CardWrap>
+      </a-col>
+    </a-row>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { queryInspectionList, ServiceRecord } from '@/api/list';
+  import useRequest from '@/hooks/request';
+  import CardWrap from './card-wrap.vue';
+
+  const defaultValue: ServiceRecord[] = new Array(3).fill({});
+  const { loading, response: renderData } = useRequest<ServiceRecord[]>(
+    queryInspectionList,
+    defaultValue
+  );
+</script>
+
+<style scoped lang="less">
+  .card-wrap {
+    height: 100%;
+    transition: all 0.3s;
+    border: 1px solid var(--color-neutral-3);
+    &:hover {
+      transform: translateY(-4px);
+    }
+    :deep(.arco-card-meta-description) {
+      color: rgb(var(--gray-6));
+      .arco-descriptions-item-label-inline {
+        font-weight: normal;
+        font-size: 12px;
+        color: rgb(var(--gray-6));
+      }
+      .arco-descriptions-item-value-inline {
+        color: rgb(var(--gray-8));
+      }
+    }
+  }
+  .empty-wrap {
+    height: 200px;
+    border-radius: 4px;
+    :deep(.arco-card) {
+      height: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      border-radius: 4px;
+      .arco-result-title {
+        color: rgb(var(--gray-6));
+      }
+    }
+  }
+</style>
diff --git a/src/views/guide/f117/components/rules-preset.vue b/src/views/guide/f117/components/rules-preset.vue
new file mode 100644
index 0000000..e5a2878
--- /dev/null
+++ b/src/views/guide/f117/components/rules-preset.vue
@@ -0,0 +1,51 @@
+<template>
+  <div class="list-wrap">
+    <a-typography-title class="block-title" :heading="6">
+      {{ $t('cardList.tab.title.preset') }}
+    </a-typography-title>
+    <a-row class="list-row" :gutter="24">
+      <a-col
+        v-for="item in renderData"
+        :key="item.id"
+        :xs="12"
+        :sm="12"
+        :md="12"
+        :lg="6"
+        :xl="6"
+        :xxl="6"
+        class="list-col"
+        style="min-height: 140px"
+      >
+        <CardWrap
+          :loading="loading"
+          :title="item.title"
+          :description="item.description"
+          :default-value="item.enable"
+          :action-type="item.actionType"
+          :tag-text="$t('cardList.preset.tag')"
+        >
+          <template #skeleton>
+            <a-skeleton :animation="true">
+              <a-skeleton-line :widths="['100%', '40%']" :rows="2" />
+              <a-skeleton-line :widths="['40%']" :rows="1" />
+            </a-skeleton>
+          </template>
+        </CardWrap>
+      </a-col>
+    </a-row>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { queryRulesPresetList, ServiceRecord } from '@/api/list';
+  import useRequest from '@/hooks/request';
+  import CardWrap from './card-wrap.vue';
+
+  const defaultValue: ServiceRecord[] = new Array(6).fill({});
+  const { loading, response: renderData } = useRequest<ServiceRecord[]>(
+    queryRulesPresetList,
+    defaultValue
+  );
+</script>
+
+<style scoped lang="less"></style>
diff --git a/src/views/guide/f117/components/the-service.vue b/src/views/guide/f117/components/the-service.vue
new file mode 100644
index 0000000..2f4748e
--- /dev/null
+++ b/src/views/guide/f117/components/the-service.vue
@@ -0,0 +1,57 @@
+<template>
+  <div class="list-wrap">
+    <a-typography-title class="block-title" :heading="6">
+      {{ $t('cardList.tab.title.service') }}
+    </a-typography-title>
+    <a-row class="list-row" :gutter="24">
+      <a-col
+        v-for="item in renderData"
+        :key="item.id"
+        :xs="12"
+        :sm="12"
+        :md="12"
+        :lg="6"
+        :xl="6"
+        :xxl="6"
+        class="list-col"
+        style="min-height: 162px"
+      >
+        <CardWrap
+          :loading="loading"
+          :title="item.title"
+          :description="item.description"
+          :default-value="item.enable"
+          :action-type="item.actionType"
+          :expires="item.expires"
+          :open-txt="$t('cardList.service.open')"
+          :close-txt="$t('cardList.service.cancel')"
+          :expires-text="$t('cardList.service.renew')"
+          :tag-text="$t('cardList.service.tag')"
+          :expires-tag-text="$t('cardList.service.expiresTag')"
+          :icon="item.icon"
+        >
+          <template #skeleton>
+            <a-skeleton :animation="true">
+              <a-skeleton-line :widths="['100%', '40%', '100%']" :rows="3" />
+              <a-skeleton-line :widths="['40%']" :rows="1" />
+            </a-skeleton>
+          </template>
+        </CardWrap>
+      </a-col>
+    </a-row>
+  </div>
+</template>
+
+<script lang="ts" setup>
+  import { queryTheServiceList, ServiceRecord } from '@/api/list';
+  import useRequest from '@/hooks/request';
+  import CardWrap from './card-wrap.vue';
+
+  const defaultValue: ServiceRecord[] = new Array(4).fill({});
+  const { loading, response: renderData } = useRequest<ServiceRecord[]>(
+    queryTheServiceList,
+    defaultValue
+  );
+</script>
+
+<style scoped lang="less"></style>
diff --git a/src/views/guide/f117/index.vue b/src/views/guide/f117/index.vue
new file mode 100644
index 0000000..3ed1814
--- /dev/null
+++ b/src/views/guide/f117/index.vue
@@ -0,0 +1,333 @@
+<template>
+  <div class="container">
+    <Breadcrumb :items="['指南', '使用117P6版']" />
+    <a-row :gutter="20" align="stretch">
+      <a-col :span="24">
+        <a-card class="general-card" title="使用117P6版">
+          <a-steps :current="state.step">
+            <a-step>选择固件类型</a-step>
+            <a-step>使用自定义固件</a-step>
+            <a-step>完成</a-step>
+          </a-steps>
+          <a-divider/>
+          <div v-show="state.step == 1" style="min-height: 300px; margin: 50px;">
+            <p style="font-size: 1.2em; font-weight: bold;">你是否想要扩容你的设备以支持中文信道存储:</p>
+            <a-radio-group type="button" v-model="state.kIt">
+              <a-radio value="yes">是的,我想要硬件改造我的设备</a-radio>
+              <a-radio value="no">不,我不需要中文信道</a-radio>
+            </a-radio-group>
+            <br>
+            <div v-show="state.kIt == 'yes'">
+              <p style="color: #ff0000; font-weight: bold;">声明:本方案及相应固件均为技术探索用途。对原机进行改造需要相应专业知识且可能存在安全和法律风险。使用本方案和固件代表您已认可风险并自行承担后果,包括且不限于财产损失、人员伤亡、违法犯罪等。</p>
+              <p style="font-weight: bold; font-size: 1.2em;">一、相关法律</p>
+              <p>
+                自制、改装、拼装的无线电发射设备,应符合国家相关技术标准,并按照工信部官网链接(<a href="http://www.miit.gov.cn/jgsj/wgj/kpzs/art/2022/art_c1ffd3c47e3f455dad38246579092136.html" target="_blank" rel="noopener noreferrer">http://www.miit.gov.cn/jgsj/wgj/kpzs/art/2022/art_c1ffd3c47e3f455dad38246579092136.html</a>,或通过工信部官网-工业和信息化部-机关司局-无管局-科普知识-《关于申请设置、使用业余无线电台所用无线电发射设备相关事宜的说明》)所列情况,提供相应材料。
+              </p>
+              <p style="font-weight: bold; font-size: 1.2em;">二、所需工具</p>
+              <p>
+                写频线、螺丝刀套装、电烙铁、焊锡丝、助焊剂、精密电器清洁剂(可选)、撬棒(可选)、万用表(可选)。
+              </p>
+              <p style="font-weight: bold; font-size: 1.2em;">三、购买扩容芯片</p>
+              <p>芯片型号:<span style="color: #ff0000;">M24M02-DRMN6</span> 或其它 SOP-8 封装 I²C 总线 2Mbit EEPROM 芯片。考虑到芯片质量及操作失误可能导致的损坏,建议多买几片备用。</p>
+            </div>
+            <div v-show="state.kIt == 'no'">
+              <p style="color: #ff0000; font-weight: bold;">声明:本方案及相应固件均为技术探索用途。对原机进行改造需要相应专业知识且可能存在安全和法律风险。使用本方案和固件代表您已认可风险并自行承担后果,包括且不限于财产损失、人员伤亡、违法犯罪等。</p>
+              <p style="font-weight: bold; font-size: 1.2em;">一、所需工具</p>
+              <p>
+                写频线。
+              </p>
+            </div>
+          </div>
+          <div v-show="state.step == 1" style="text-align: center;">
+            <a-button type="primary" :disabled="!state.kIt" @click="state.step = 2">下一步</a-button>
+          </div>
+          <div v-show="state.step == 2" style="min-height: 300px; text-align: center; color: #C9CDD4; ">
+            <a-spin :loading="state.loading" tip="处理中..." style="width: 100%;">
+              <a-collapse :activeKey="state.flashStep" @change="(e: any)=>{state.flashStep = e; if(e[0] == 'six'){state.finish = true;}}" accordion>
+                <a-collapse-item header="备份原机数据" key="one">
+                  <div style="text-align: left;">
+                    <p>在对设备进行操作前,应备份原机出厂的配置、校准数据,以保证发射性能符合国家标准。(请妥善保存备份文件)</p>
+                    <p>
+                      <a-space>
+                        <a-button type="primary" :disabled="state.backed" @click="backupIt">备份</a-button><span v-show="state.backed" style="color: #C9CDD4;">已备份✔</span>&nbsp;
+                        <a-button type="primary" :disabled="!state.backed" @click="state.flashStep[0] = 'two'">下一步</a-button>
+                      </a-space>
+                    </p>
+                  </div>
+                </a-collapse-item>
+                <a-collapse-item header="拆机" key="two">
+                  <div style="text-align: left;">
+                    <p>视频教程链接:
+                      <a href="https://www.bilibili.com/video/BV1ib4y137Ah" target="_blank" rel="noopener noreferrer">https://www.bilibili.com/video/BV1ib4y137Ah</a>
+                      <span style="color: #ff0000;">(硬件拆解及焊接部分可参照本视频,软件刷机部分请务必以本指南为准。)</span>
+                    </p>
+                    <div>
+                      ①拆掉电池、天线和旋钮盖。<br>
+                      ②用撬棒插入主机背面底部正中位置缝隙,向上撬出铝制背板。③向下后方抽出背板。前盖和主板间有扬声器导线连接,此处用力不要过猛,控制幅度,以免拉断导线。<br>
+                      ④拿掉耳机口挡板。<br>
+                      ⑤建议将扬声器导线拆焊,以免阻碍后续拆解和焊接,导致拉断导线。最后组装时再对扬声器导线进行焊接。<br>
+                      ⑥拆卸屏幕(难点,请认真看视频教程!)。在屏幕左下角卡扣位置,用撬棒向内按压同时向上抬起即可拆卸左侧,屏幕左侧松脱后另一侧拆卸相对简单。此处用力不要过猛,控制幅度,屏幕完全拆卸后应妥善固定,以免拉断、折断背面上方排线。<br>
+                      ⑦拆卸全部 5 颗螺丝并分离背板。
+                    </div>
+                    <img :src="cj1">
+                    <div>
+                      需要更换的芯片位于主板背面右下角,型号为 BL24C64A。
+                    </div>
+                    <img :src="cj2">
+                    <div>
+                      ①拆焊及焊接。有动手能力的朋友自行操作,要求芯片方向正确(以第1 脚圆点为准),焊点饱满,无虚焊、短路,芯片周围的电子元器件保持完好。建议用300℃以上小刀头烙铁配合助焊剂,不建议用热风枪(高手除外)。手残党可以去手机维修店更换,费用5-30 元不等。若周围元件遭到破坏,可按下图参数更换补救。
+                    </div>
+                    <img :src="cj3">
+                    <div>
+                      ②将主板装回背板。(背板上 3x5mm 导热硅胶垫若脱落,请务必装回对应凸起位置;电池触点部分过背板孔时当心压弯。)<br>
+                      ③装入电池,按住 PTT 键开机,进入刷机模式(此时手电筒常亮、屏幕无显示)。如无法进入刷机模式,检查电池接触片是否错位、焊点及周边元件是否完好。
+                    </div>
+                    <div style="color: #ff0000; font-weight: bold;">
+                      此时先不要完全组装手台,待后续工作全部完成后再行组装,以便故障返工。此阶段如尝试正常开机后显示异常、电量异常、接收异常等均为正常情况,不用担心。后续操作后会恢复正常。
+                    </div>
+                    <div>
+                      <a-button type="primary" @click="state.flashStep[0] = 'three'">下一步</a-button>
+                    </div>
+                  </div>
+                </a-collapse-item>
+                <a-collapse-item header="刷入固件" key="three">
+                  <div style="text-align: left;">
+                    <p>断开写频线,按住 PTT 键开机,进入刷机模式(此时手电筒常亮、屏幕无显示),手电筒常亮后插回写频线。</p>
+                    <p>
+                      <a-space>
+                        <a-button type="primary" :disabled="state.flashIt" @click="iFlashIt">刷入固件</a-button>
+                        <a-button type="primary" :disabled="!state.flashIt" @click="state.flashStep[0] = 'four'">下一步</a-button>
+                      </a-space>
+                    </p>
+                  </div>
+                </a-collapse-item>
+                <a-collapse-item header="刷回原机数据" key="four">
+                  <div style="text-align: left;">
+                    <p>正常开机,使设备处于开机状态,点击刷回备份数据。</p>
+                    <p>
+                      <a-space>
+                        <a-button type="primary" :disabled="state.restoreBackupIt" @click="restoreBackup">刷回备份数据</a-button>
+                        <a-button type="primary" :disabled="!state.restoreBackupIt" @click="state.flashStep[0] = 'five'">下一步</a-button>
+                      </a-space>
+                    </p>
+                  </div>
+                </a-collapse-item>
+                <a-collapse-item header="刷入字库" key="five">
+                  <div style="text-align: left;">
+                    <p>正常开机,使设备处于开机状态,点击刷入字库。</p>
+                    <p>
+                      <a-space>
+                        <a-button type="primary" :disabled="state.flashFontIt" @click="flashFont">刷入字库</a-button>
+                        <a-button type="primary" :disabled="!state.flashFontIt" @click="state.flashStep[0] = 'six'">下一步</a-button>
+                      </a-space>
+                    </p>
+                  </div>
+                </a-collapse-item>
+                <a-collapse-item header="完全组装" key="six">
+                  <div style="text-align: left;">按拆解顺序进行焊接和组装。</div>
+                </a-collapse-item>
+              </a-collapse>
+            </a-spin>
+          </div>
+          <div v-show="state.step == 2" style="text-align: center;">
+            <a-space>
+              <a-button :disabled="!state.kIt" @click="state.step = 1">上一步</a-button>
+              <a-button type="primary" :disabled="!state.finish" @click="state.step = 3">完成</a-button>
+            </a-space>
+          </div>
+          <div v-show="state.step == 3" style="min-height: 300px; text-align: center; color: #C9CDD4; ">
+            <a-result
+              class="result"
+              status="success"
+              subtitle="刷入成功"
+            />
+            <a-button type="primary" @click="()=>{router.push('/chirp/base');}">
+              返回首页
+            </a-button>
+          </div>
+        </a-card>
+      </a-col>
+    </a-row>
+  </div>
+</template>
+
+<script lang="ts" setup>
+import { reactive, nextTick } from 'vue';
+import { useRouter, useRoute } from 'vue-router';
+import { useAppStore } from '@/store';
+import { eeprom_write, eeprom_reboot, eeprom_init, eeprom_read, flash_flashFirmware, connect, disconnect, sendPacket, unpackVersion, readPacket, unpack } from '@/utils/serial.js';
+import cj1 from './assets/cj1.png'
+import cj2 from './assets/cj2.png'
+import cj3 from './assets/cj3.png'
+
+const appStore = useAppStore();
+const router = useRouter();
+
+const state : {
+  step: any,
+  flashStep: any,
+  backed: any,
+  kIt: any,
+  flashIt: any,
+  restoreBackupIt: any,
+  loading: any,
+  flashFontIt: any,
+  finish: any
+} = reactive({
+  step: 1,
+  flashStep: ['one'],
+  backed: undefined,
+  kIt: undefined,
+  flashIt: false,
+  restoreBackupIt: false,
+  loading: false,
+  flashFontIt: false,
+  finish: false
+})
+
+const backupRange = async (start: any, end: any, name: any = new Date() + '_backup.bin') =>{
+  await eeprom_init(appStore.connectPort);
+  let rawEEPROM = new Uint8Array(end - start);
+  for (let i = start; i < end; i += 0x80) {
+    const data = await eeprom_read(appStore.connectPort, i);
+    rawEEPROM.set(data, i - start);
+  }
+  const blob = new Blob([rawEEPROM], { type: 'application/octet-stream' });
+  const url = URL.createObjectURL(blob);
+  const a = document.createElement('a');
+  a.href = url;
+  a.download = name;
+  document.body.appendChild(a);
+  a.click();
+  document.body.removeChild(a);
+  URL.revokeObjectURL(url);
+  return rawEEPROM;
+}
+
+const restoreRange = async (start: any = 0, uint8Array: any) => {
+  await eeprom_init(appStore.connectPort);
+  for (let i = start; i < uint8Array.length + start; i += 0x80) {
+    await eeprom_write(appStore.connectPort, i, uint8Array.slice(i - start, i - start + 0x80));
+  }
+  await eeprom_reboot(appStore.connectPort);
+}
+
+const backupIt = async () => {
+  if(appStore.connectState != true){alert('点击右上角“连接”按钮连接手台。'); return;};
+  state.loading = true
+  state.backed = await backupRange(0, 0x2000);
+  console.log(state.backed)
+  state.loading = false
+}
+
+const iFlashIt = async () => {
+  state.loading = true
+  let fontPacket = undefined
+  if(state.kIt){
+    fontPacket = await fetch('/LOSEHU117P6K.bin')
+  }else{
+    fontPacket = await fetch('/LOSEHU117P6.bin')
+  }
+  const reader = fontPacket.body.getReader();
+  const chunks = [];
+  while(true) {
+    const {done, value} = await reader.read();
+    if (done) {
+      break;
+    }
+    chunks.push(...value)
+  }
+  const binary = new Uint8Array(chunks)
+  if(appStore.connectPort){
+    await disconnect(appStore.connectPort);
+  }
+  let _connect = await connect();
+  await readPacket(_connect, 0x18, 1000);
+  const rawVersion = unpackVersion(binary);
+  const _data = new Uint8Array([0x30, 0x5, rawVersion.length, 0x0, ...rawVersion]);
+  await sendPacket(_connect, _data);
+  await readPacket(_connect, 0x18)
+  await flash_flashFirmware(_connect, unpack(binary))
+  appStore.updateSettings({ connectPort: _connect });
+  state.flashIt = true
+  state.loading = false
+}
+
+const restoreBackup = async () => {
+  if(appStore.connectState != true){alert('点击右上角“连接”按钮连接手台。'); return;};
+  state.loading = true
+  await restoreRange(0, state.backed);
+  state.restoreBackupIt = true;
+  state.loading = false
+}
+
+const flashFont = async () => {
+  state.loading = true
+  const fontPacket = await fetch('/old_font.bin')
+  const reader = fontPacket.body.getReader();
+  const chunks = [];
+  while(true) {
+    const {done, value} = await reader.read();
+    if (done) {
+      break;
+    }
+    chunks.push(...value)
+  }
+  const binary = new Uint8Array(chunks)
+  await restoreRange(0x02000, binary)
+  state.flashFontIt = true;
+  state.loading = false
+}
+
+</script>
+
+<script lang="ts">
+  export default {
+    name: 'Chi',
+  };
+</script>
+
+<style scoped lang="less">
+  .container {
+    padding: 0 20px 20px 20px;
+    :deep(.arco-list-content) {
+      overflow-x: hidden;
+    }
+
+    :deep(.arco-card-meta-title) {
+      font-size: 14px;
+    }
+  }
+  :deep(.arco-list-col) {
+    display: flex;
+    flex-direction: row;
+    flex-wrap: wrap;
+    justify-content: space-between;
+  }
+
+  :deep(.arco-list-item) {
+    width: 33%;
+  }
+
+  :deep(.block-title) {
+    margin: 0 0 12px 0;
+    font-size: 14px;
+  }
+  :deep(.list-wrap) {
+    // min-height: 140px;
+    .list-row {
+      align-items: stretch;
+      .list-col {
+        margin-bottom: 16px;
+      }
+    }
+    :deep(.arco-space) {
+      width: 100%;
+      .arco-space-item {
+        &:last-child {
+          flex: 1;
+        }
+      }
+    }
+  }
+</style>
diff --git a/src/views/guide/f117/locale/en-US.ts b/src/views/guide/f117/locale/en-US.ts
new file mode 100644
index 0000000..6b0681e
--- /dev/null
+++ b/src/views/guide/f117/locale/en-US.ts
@@ -0,0 +1,19 @@
+export default {
+  'menu.list.cardList': 'Card List',
+  'cardList.tab.title.all': 'All',
+  'cardList.tab.title.content': 'Quality Inspection',
+  'cardList.tab.title.service': 'The service',
+  'cardList.tab.title.preset': 'Rules Preset',
+  'cardList.searchInput.placeholder': 'Search',
+  'cardList.enable': 'Enable',
+  'cardList.disable': 'Disable',
+  'cardList.content.delete': 'Delete',
+  'cardList.content.inspection': 'Inspection',
+  'cardList.content.action': 'Click Create Qc Content queue',
+  'cardList.service.open': 'Open',
+  'cardList.service.cancel': 'Cancel',
+  'cardList.service.renew': 'Contract of service',
+  'cardList.service.tag': 'Opened',
+  'cardList.service.expiresTag': 'Expired',
+  'cardList.preset.tag': 'Enable',
+};
diff --git a/src/views/guide/f117/locale/zh-CN.ts b/src/views/guide/f117/locale/zh-CN.ts
new file mode 100644
index 0000000..0cd29e7
--- /dev/null
+++ b/src/views/guide/f117/locale/zh-CN.ts
@@ -0,0 +1,19 @@
+export default {
+  'menu.list.cardList': '卡片列表',
+  'cardList.tab.title.all': '全部',
+  'cardList.tab.title.content': '内容质检',
+  'cardList.tab.title.service': '开通服务',
+  'cardList.tab.title.preset': '规则预置',
+  'cardList.searchInput.placeholder': '搜索',
+  // 'cardList.statistic.enable': '已启用',
+  // 'cardList.statistic.disable': '未启用',
+  'cardList.content.delete': '删除',
+  'cardList.content.inspection': '质检',
+  'cardList.content.action': '点击创建质检内容队列',
+  'cardList.service.open': '开通服务',
+  'cardList.service.cancel': '取消服务',
+  'cardList.service.renew': '续约服务',
+  'cardList.service.tag': '已开通',
+  'cardList.service.expiresTag': '已过期',
+  'cardList.preset.tag': '已启用',
+};
diff --git a/src/views/guide/f117/mock.ts b/src/views/guide/f117/mock.ts
new file mode 100644
index 0000000..68cf096
--- /dev/null
+++ b/src/views/guide/f117/mock.ts
@@ -0,0 +1,186 @@
+import Mock from 'mockjs';
+import setupMock, { successResponseWrap } from '@/utils/setup-mock';
+import { ServiceRecord } from '@/api/list';
+
+const qualityInspectionList: ServiceRecord[] = [
+  {
+    id: 1,
+    name: 'quality',
+    title: '视频类-历史导入',
+    description: '2021-10-12 00:00:00',
+    data: [
+      {
+        label: '待质检数',
+        value: '120',
+      },
+      {
+        label: '积压时长',
+        value: '60s',
+      },
+      {
+        label: '待抽检数',
+        value: '0',
+      },
+    ],
+  },
+  {
+    id: 2,
+    name: 'quality',
+    title: '图文类-图片版权',
+    description: '2021-12-11 18:30:00',
+    data: [
+      {
+        label: '待质检数',
+        value: '120',
+      },
+      {
+        label: '积压时长',
+        value: '60s',
+      },
+      {
+        label: '待抽检数',
+        value: '0',
+      },
+    ],
+  },
+  {
+    id: 3,
+    name: 'quality',
+    title: '图文类-高清图片',
+    description: '2021-10-15 08:10:00',
+    data: [
+      {
+        label: '待质检数',
+        value: '120',
+      },
+      {
+        label: '积压时长',
+        value: '60s',
+      },
+      {
+        label: '待抽检数',
+        value: '0',
+      },
+    ],
+  },
+];
+const theServiceList: ServiceRecord[] = [
+  {
+    id: 1,
+    icon: 'code',
+    title: '漏斗分析',
+    description:
+      '用户行为分析之漏斗分析模型是企业实现精细化运营、进行用户行为分析的重要数据分析模型。',
+    enable: true,
+    actionType: 'button',
+  },
+  {
+    id: 2,
+    icon: 'edit',
+    title: '用户分布',
+    description:
+      '快速诊断用户人群,地域细分情况,了解数据分布的集中度,以及主要的数据分布的区间段是什么。',
+    enable: true,
+    actionType: 'button',
+    expires: true,
+  },
+  {
+    id: 3,
+    icon: 'user',
+    title: '资源分发',
+    description:
+      '移动端动态化资源分发解决方案。提供稳定大流量服务支持、灵活定制的分发圈选规则,通过离线化预加载。',
+    enable: false,
+    actionType: 'button',
+  },
+  {
+    id: 4,
+    icon: 'user',
+    title: '用户画像分析',
+    description:
+      '用户画像就是将典型用户信息标签化,根据用户特征、业务场景和用户行为等信息,构建一个标签化的用户模型。',
+    enable: true,
+    actionType: 'button',
+  },
+];
+const rulesPresetList: ServiceRecord[] = [
+  {
+    id: 1,
+    title: '内容屏蔽规则',
+    description:
+      '用户在执行特定的内容分发任务时,可使用内容屏蔽规则根据特定标签,过滤内容集合。',
+    enable: true,
+    actionType: 'switch',
+  },
+  {
+    id: 2,
+    title: '内容置顶规则',
+    description:
+      '该规则支持用户在执行特定内容分发任务时,对固定的几条内容置顶。',
+    enable: true,
+    actionType: 'switch',
+  },
+  {
+    id: 3,
+    title: '内容加权规则',
+    description: '选定内容加权规则后可自定义从不同内容集合获取内容的概率。',
+    enable: false,
+    actionType: 'switch',
+  },
+  {
+    id: 4,
+    title: '内容分发规则',
+    description: '内容分发时,对某些内容需要固定在C端展示的位置。',
+    enable: true,
+    actionType: 'switch',
+  },
+  {
+    id: 5,
+    title: '违禁内容识别',
+    description: '精准识别赌博、刀枪、毒品、造假、贩假等违规物品和违规行为。',
+    enable: false,
+    actionType: 'switch',
+  },
+  {
+    id: 6,
+    title: '多语言文字符号识别',
+    description:
+      '精准识别英语、维语、藏语、蒙古语、朝鲜语等多种语言以及emoji表情形态的语义识别。',
+    enable: false,
+    actionType: 'switch',
+  },
+];
+
+setupMock({
+  setup() {
+    // Quality Inspection
+    Mock.mock(new RegExp('/api/list/quality-inspection'), () => {
+      return successResponseWrap(
+        qualityInspectionList.map((_, index) => ({
+          ...qualityInspectionList[index % qualityInspectionList.length],
+          id: Mock.Random.guid(),
+        }))
+      );
+    });
+
+    // the service
+    Mock.mock(new RegExp('/api/list/the-service'), () => {
+      return successResponseWrap(
+        theServiceList.map((_, index) => ({
+          ...theServiceList[index % theServiceList.length],
+          id: Mock.Random.guid(),
+        }))
+      );
+    });
+
+    // rules preset
+    Mock.mock(new RegExp('/api/list/rules-preset'), () => {
+      return successResponseWrap(
+        rulesPresetList.map((_, index) => ({
+          ...rulesPresetList[index % rulesPresetList.length],
+          id: Mock.Random.guid(),
+        }))
+      );
+    });
+  },
+});