Browse Source

fix: 分段

lvkun996 1 year ago
parent
commit
0f8cf8af91

File diff suppressed because it is too large
+ 370 - 175
package-lock.json


+ 14 - 2
package.json

@@ -11,15 +11,26 @@
     "upload:dev": "python upload.py /usr/share/nginx/html/ report dev"
   },
   "dependencies": {
+    "-": "0.0.1",
     "axios": "^1.5.0",
+    "docx-preview": "^0.3.2",
+    "docxtemplater": "^3.47.2",
+    "docxtemplater-image-module-free": "^1.1.1",
     "echarts": "^5.5.0",
+    "file-saver": "^2.0.5",
     "flicker-vue-hooks": "^1.0.58",
+    "html-docx-js-typescript": "^0.1.5",
     "html2canvas": "^1.4.1",
     "jspdf": "^2.5.1",
+    "jszip-utils": "^0.1.0",
     "lottie-web": "^5.12.2",
     "pdfjs-dist": "^4.0.189",
     "pdfmake": "^0.3.0-beta.1",
     "pinia": "^2.1.6",
+    "pizzip": "^3.1.7",
+    "qrcode": "^1.5.3",
+    "qrcodejs2": "0.0.2",
+    "s": "^1.0.0",
     "sass": "^1.67.0",
     "sass-loader": "^13.3.2",
     "vant": "^4.6.8",
@@ -29,16 +40,17 @@
   },
   "devDependencies": {
     "@types/node": "^20.9.0",
+    "@vitejs/plugin-legacy": "^4.1.0",
     "@vitejs/plugin-vue": "^4.2.3",
     "@xianzhengquan/postcss-px-2-vw": "^0.0.1",
     "eslint": "^8.49.0",
     "eslint-plugin-vue": "^9.17.0",
     "postcss-px-to-viewport": "^1.1.1",
     "prettier": "^3.0.3",
+    "rollup-plugin-terser": "^7.0.2",
     "ts-node": "^10.9.1",
     "typescript": "^5.0.2",
     "vite": "^4.4.5",
-    "vue-tsc": "^1.8.5",
-    "@vitejs/plugin-legacy": "^4.1.0"
+    "vue-tsc": "^1.8.5"
   }
 }

BIN
public/termplate.docx


+ 2 - 1
src/components/chatComponent.vue

@@ -4,7 +4,7 @@
       <div class="chat-component-item-children" >
         <div class="baby" :style="{justifyContent: index % 2 ? 'flex-end' : 'flex-start' }">
           <img class="avator" :src="item.headImg" v-if="index % 2 === 0" />
-          <div :class="['chat-content', index % 2 ? 'chat-content-right' : 'chat-content-left']"> {{item.content}}</div>
+          <div :class="['chat-content', index % 2 ? 'chat-content-right' : 'chat-content-left']">{{item.babyName}}:  {{item.content}}</div>
           <img class="avator" :src="item.headImg" v-if="index % 2 !== 0"/>
         </div>
         <div class="video" >
@@ -68,6 +68,7 @@ defineProps<IProps>()
           position: relative;
           max-width: 180px;
           margin-right: 20px;
+          font-size: 14px;
         }
         .chat-content-left::before {
           content: "";

+ 1 - 1
src/components/gridImageComponent.vue

@@ -32,7 +32,7 @@ defineProps<IProps>()
  margin-top: 14px;
  > div {
    margin-left: 7px;
-   flex: 1;
+   width: 90px;
    height: 90px;
    border-radius: 8px 8px 8px 8px;
    background-repeat: no-repeat;

+ 1 - 1
src/router/index.ts

@@ -62,7 +62,7 @@ const router = createRouter({
       meta: {
         title: "问题记录单次报告",
       },
-    },
+    }
   ],
 });
 

+ 3 - 3
src/store/modules/customize.ts

@@ -49,7 +49,7 @@ export default defineStore("customize", () => {
   async function getSingleRecord(babyId: string, id: string) {
     const { status, data } = await getCustomizeSingleRecord(babyId, id);
     if (status === 200) {
-      singleRecord.value = data;
+      singleRecord.value = data
     }
   }
   function sendToParent() {
@@ -66,7 +66,7 @@ export default defineStore("customize", () => {
     const { status, data } = await getCustomizeSemesterBabies(params);
     if (status === 200) {
       recordData.value = data.recordData;
-      babyList.value = data.babyList;
+      babyList.value = data.babyList
     }
   }
 
@@ -92,7 +92,7 @@ export default defineStore("customize", () => {
         ...data.entityList.map((item) => {
           return {
             ...item,
-            isExpand: item.abilityList.length < 4,
+            isExpand: item.abilityList.length < 4
           };
         }),
       );

+ 1 - 1
src/views/customize/GrowthRecord.vue

@@ -75,7 +75,7 @@
             class="dialogue-text flex-center"
           >
             <!-- {{ ability.domainName }}{{ ability.abilityName ? "-" + ability.abilityName : ability.abilityName }} -->
-            {{ ability.domainName }} {{ability.domainName? '-' : ''}} {{ ability.abilityName }} - {{ability.label}}
+            {{ ability.domainName }} {{ability.domainName? '-' : ''}} {{ ability.abilityName }} {{ability.label ? '-' : ''}} {{ability.label}}
           </div>
         </div>
 

+ 4 - 7
src/views/customize/ProblemRecord.vue

@@ -132,8 +132,10 @@ onMounted(async () => {
       </div>
       <img :src="getImageUrl('inquiry_process_logo')" alt="" class="pr-content-logo" />
       <div class="pr-content-process">
-        <div class="process" v-for="(record, index) in [question.plan, question.practice, question.summary, question.tweaks, question.topic]">
-          <div class="process-title">
+        <div 
+          class="process" v-for="(record, index) in [question.plan, question.practice, question.summary, question.tweaks, question.topic]"
+        >
+          <div class="process-title" v-if="( record.content || record.videos.length > 0 || record.images.length > 0 || record.children.length > 0)">
             <div class="process-title-dot"></div>
             <div class="process-title-text">{{getLabel(index)}}</div>
           </div>
@@ -158,11 +160,6 @@ onMounted(async () => {
             >
             </div>
           </div>
-
-          <div class="process-title" style="margin-top: 16px;">
-            <div class="process-title-dot"></div>
-            <div class="process-title-text">{{getLabel(4)}}</div>
-          </div>
     
           <div class="pr-content-chat" v-if="record.children.length > 0" >
             <chatComponent :chat="record.children"  />

+ 212 - 148
src/views/customize/SingleReport.vue

@@ -1,150 +1,12 @@
-<script setup lang="ts">
-import { getImageUrl, imagePreview } from "@/utils";
-import { useCustomizeStore } from "@/store";
-import { useRoute } from "vue-router";
-import HeaderPart from "@/views/customize/components/HeaderPart.vue";
-import { ISingleRecord } from "@/types/customize.d";
-import { computed, onMounted, ref } from "vue";
-import ConfirmJoin from "@/views/customize/components/ConfirmJoin.vue";
-
-const { b: babyId, i: id, p: isParent, o: isMyRoute } = useRoute().query;
-
-const customizeStore = useCustomizeStore();
-
-const { getSingleRecord, sendSingleRecord, joinSemesterReport } = customizeStore;
-
-const showConfirm = ref(false);
-
-const singleRecord = computed<ISingleRecord>(() => {
-  // return customizeStore.singleRecord;
-  return  {
-        "recordId": "1782591626363899905",
-        "babyId": "1382262152722042882",
-        "sendReport": 0,
-        "recordDate": "2024-04-23",
-        "teacherName": "雷lEi\uD83C\uDF88",
-        "babyNames": [
-            "王凯昊"
-        ],
-        "joinSemester": 1,
-        "sceneName": "区域活动",
-        "story": {
-            "images": [
-                "https://app-resources-luojigou.luojigou.vip/3649ac71b59e4fb1b8c025928c70e37e"
-            ],
-            "videos": [],
-            "content": ""
-        },
-        "domainList": [
-            {
-                "domainName": "逻辑思维通用领域",
-                "abilityList": [
-                    {
-                        "abilityName": "批判思维能力",
-                        "description": {
-                            "growth": "S1.在成人的帮助下,幼儿能够模仿其他学生的作品(例:运用自己喜欢的相似的材料)。",
-                            "behave": "看到其他小朋友在画上贴贴纸,他也会找喜欢的贴纸。",
-                            "behaviours": [],
-                            "example": "无",
-                            "tactics": "【环创】:\n1.创作机会和条件,支持幼儿自发的艺术表现和创造。\n2.为幼儿提供丰富的便于幼儿取放的材料、工具或物品。\n【师幼】:\n1.和幼儿一起发现并感受周边事物的特征。\n2.倾听幼儿的所见所想,鼓励幼儿大胆表达。",
-                            "education": "1.家长可以和幼儿一起玩串珠的游戏,家长示范,幼儿模仿。\n2.家长可以和幼儿一起进行阅读绘本,一起模仿绘本里动物或者人物的动作、声音和表情等。"
-                        },
-                        "descriptionExtra": {
-                            "growth": "",
-                            "behave": "2024年04月23日,在来来来,王凯昊、朱婷、陈树炎、李佩、雷晴、刘雯雯、孙艺洲、钱小豪、宝宝、田甜小朋友正在来来来活动。\n看到其他小朋友在画上贴贴纸,他也会找喜欢的贴纸。",
-                            "behaviours": [],
-                            "example": "",
-                            "tactics": "来来来12345",
-                            "education": ""
-                        }
-                    }
-                ]
-            }
-        ]
-  }
-});
-const isImages = computed(() => {
-  return singleRecord.value.story?.images.length !== 0;
-});
-const isVideo = computed(() => {
-  return singleRecord.value.story?.videos.length !== 0;
-});
-const isContent = computed(() => {
-  return singleRecord.value.story?.content && singleRecord.value.story?.content.length !== 0;
-});
-const isHas = computed(() => {
-  return singleRecord.value.story && (isImages.value || isVideo.value || isContent.value);
-});
-
-async function confirm() {
-  // console.log("confirm");
-  await joinSemesterReport(singleRecord.value.recordId, 0);
-  showConfirm.value = false;
-}
-
-function formatNames(names: string[]) {
-  let str = "";
-  names.map((name, nameIndex) => {
-    if (nameIndex !== 0) {
-      str += "、";
-    }
-    str += name;
-  });
-  return str;
-}
-
-function send() {
-  if (singleRecord.value.sendReport) return;
-  if (typeof babyId === "string" && typeof id === "string")
-    sendSingleRecord({
-      babyId,
-      id,
-      recordType: "1",
-    });
-}
-
-function changeJoinState() {
-  if (singleRecord.value.joinSemester === 1) {
-    showConfirm.value = true;
-  } else {
-    joinSemesterReport(singleRecord.value.recordId, 1);
-  }
-}
-
-function getStyle(index: number, length: number) {
-  return {
-    borderRadius: `${
-      length === 1 ? "17px" : index === 0 ? "17px 17px 0 0" : index === length - 1 ? "0 0 17px 17px" : "0"
-    }`,
-    borderTop: `${index === 0 ? "4px solid #b6dcff" : "none"}`,
-    borderBottom: `${index === length - 1 ? "4px solid #b6dcff" : "none"}`,
-    boxShadow: `${index === 0 ? "0 1px 6px 0 rgba(42, 105, 253, 0.16)" : "none"}`,
-  };
-}
-
-onMounted(async () => {
-  // await getTestTeachToken();
-
-  if (typeof babyId === "string" && typeof id === "string") await getSingleRecord(babyId, id);
-});
-
-/**
- * 行为记录单次报告
- *
- * b: babyId
- * i: recordId
- *
- * http://192.168.1.17:8989/#/customize/SingleReport?b=1304672468777553921&i=1716737882201264130
- */
-</script>
 
 <template>
   <div class="single-report">
     <HeaderPart 
       :isParent="isParent" 
-      exportTable
-      :isMyRoute="isMyRoute" 
-      @send="send" 
+      :exportWord="true"
+      :isMyRoute="isMyRoute"
+      :record="singleRecord"
+      @send="send"
       @change="changeJoinState" 
     />
 
@@ -214,7 +76,7 @@ onMounted(async () => {
             :poster="singleRecord.story?.videos[0] + '?vframe/jpg/offset/1'"
             class="video"
           ></video>
-          <div v-if="isContent" class="content">{{ singleRecord.story?.content }}</div>
+          <div v-if="isContent" class="content"  >{{ singleRecord.story?.content }}</div>
         </div>
       </div>
 
@@ -230,20 +92,19 @@ onMounted(async () => {
           <div class="title">能力名称:{{ ability.abilityName }}</div>
          <template v-if="!isContent"  >
           <img :src="getImageUrl('desc_name_02')" alt="" class="name" />
-          <div class="desc green">{{ ability.descriptionExtra?.behave || ability.description.behave }}</div>
+          <div class="desc green" v-html="ability.descriptionExtra?.behave.replaceAll('\n', '<br/>') || ability.description.behave.replaceAll('\n', '<br/>')"></div>
          </template>
           
           <img :src="getImageUrl('desc_name_01')" alt="" class="name" />
-          <div class="desc">{{ ability.description.growth }}</div>
+          <div class="desc" v-html="ability.description.growth.replaceAll('\n', '<br/>')"></div>
     
         
           <img :src="getImageUrl('family_title')" alt="" class="name" />
           <div class="strategy">
             <div class="strategy-title">一日活动</div>
-            <div class="strategy-content">{{ ability.descriptionExtra?.tactics || ability.description.tactics }}</div>
+            <div class="strategy-content" v-html="ability.descriptionExtra?.tactics.replaceAll('\n', '<br/>') || ability.description.tactics.replaceAll('\n', '<br/>')"></div>
             <div class="strategy-title">家园共育</div>
-            <div class="strategy-content">
-              {{ ability.descriptionExtra?.education || ability.description.education }}
+            <div class="strategy-content" v-html="ability.descriptionExtra!.education.replaceAll('\n', '<br/>') || ability.description.education.replaceAll('\n', '<br/>') " >
             </div>
           </div>
         </div>
@@ -254,6 +115,209 @@ onMounted(async () => {
   <ConfirmJoin :show="showConfirm" @close="showConfirm = false" @confirm="confirm" />
 </template>
 
+<script setup lang="ts">
+import { getImageUrl, imagePreview } from "@/utils";
+import { useCustomizeStore } from "@/store";
+import { useRoute } from "vue-router";
+import HeaderPart from "@/views/customize/components/HeaderPart.vue";
+import { ISingleRecord } from "@/types/customize.d";
+import { computed, onMounted, ref } from "vue";
+import ConfirmJoin from "@/views/customize/components/ConfirmJoin.vue";
+
+const { b: babyId, i: id, p: isParent, o: isMyRoute } = useRoute().query;
+
+const customizeStore = useCustomizeStore();
+
+const { getSingleRecord, sendSingleRecord, joinSemesterReport } = customizeStore;
+
+const showConfirm = ref(false);
+
+const singleRecord = computed<ISingleRecord>(() => {
+  return customizeStore.singleRecord;
+  // return  {
+  //       "recordId": "1782591626363899905",
+  //       "babyId": "1382262152722042882",
+  //       "sendReport": 0,
+  //       "recordDate": "2024-04-23",
+  //       "teacherName": "雷lEi\uD83C\uDF88",
+  //       "babyNames": [
+  //           "王凯昊"
+  //       ],
+  //       "joinSemester": 1,
+  //       "sceneName": "区域活动",
+  //       "story": {
+  //           "images": [
+  //               "https://app-resources-luojigou.luojigou.vip/3649ac71b59e4fb1b8c025928c70e37e"
+  //           ],
+  //           "videos": ['https://app-resources-luojigou.luojigou.vip/3649ac71b59e4fb1b8c025928c70e37e'],
+  //           "content": ""
+  //       },
+  //       "domainList": [
+  //           {
+  //               "domainName": "逻辑思维通用领域",
+  //               "abilityList": [
+  //                   {
+  //                       "abilityName": "批判思维能力",
+  //                       "description": {
+  //                           "growth": "S1.在成人的帮助下,幼儿能够模仿其他学生的作品(例:运用自己喜欢的相似的材料)。",
+  //                           "behave": "看到其他小朋友在画上贴贴纸,他也会找喜欢的贴纸。",
+  //                           "behaviours": [],
+  //                           "example": "无",
+  //                           "tactics": "【环创】:\n1.创作机会和条件,支持幼儿自发的艺术表现和创造。\n2.为幼儿提供丰富的便于幼儿取放的材料、工具或物品。\n【师幼】:\n1.和幼儿一起发现并感受周边事物的特征。\n2.倾听幼儿的所见所想,鼓励幼儿大胆表达。",
+  //                           "education": "1.家长可以和幼儿一起玩串珠的游戏,家长示范,幼儿模仿。\n2.家长可以和幼儿一起进行阅读绘本,一起模仿绘本里动物或者人物的动作、声音和表情等。"
+  //                       },
+  //                       "descriptionExtra": {
+  //                           "growth": "",
+  //                           "behave": "2024年04月23日,在来来来,王凯昊、朱婷、陈树炎、李佩、雷晴、刘雯雯、孙艺洲、钱小豪、宝宝、田甜小朋友正在来来来活动。\n看到其他小朋友在画上贴贴纸,他也会找喜欢的贴纸。",
+  //                           "behaviours": [],
+  //                           "example": "",
+  //                           "tactics": "来来来12345",
+  //                           "education": ""
+  //                       }
+  //                   },
+  //                   {
+  //                       "abilityName": "很强大的学习能力",
+  //                       "description": {
+  //                           "growth": "S1.在成人的帮助下,幼儿能够模仿其他学生的作品(例:运用自己喜欢的相似的材料)。",
+  //                           "behave": "看到其他小朋友在画上贴贴纸,他也会找喜欢的贴纸。",
+  //                           "behaviours": [],
+  //                           "example": "无",
+  //                           "tactics": "【环创】:\n1.创作机会和条件,支持幼儿自发的艺术表现和创造。\n2.为幼儿提供丰富的便于幼儿取放的材料、工具或物品。\n【师幼】:\n1.和幼儿一起发现并感受周边事物的特征。\n2.倾听幼儿的所见所想,鼓励幼儿大胆表达。",
+  //                           "education": "1.家长可以和幼儿一起玩串珠的游戏,家长示范,幼儿模仿。\n2.家长可以和幼儿一起进行阅读绘本,一起模仿绘本里动物或者人物的动作、声音和表情等。"
+  //                       },
+  //                       "descriptionExtra": {
+  //                           "growth": "",
+  //                           "behave": "2024年04月23日,在来来来,王凯昊、朱婷、陈树炎、李佩、雷晴、刘雯雯、孙艺洲、钱小豪、宝宝、田甜小朋友正在来来来活动。\n看到其他小朋友在画上贴贴纸,他也会找喜欢的贴纸。",
+  //                           "behaviours": [],
+  //                           "example": "",
+  //                           "tactics": "来来来12345",
+  //                           "education": ""
+  //                       }
+  //                   },
+  //               ]
+  //           },
+  //           {
+  //               "domainName": "科学探索通用领域",
+  //               "abilityList": [
+  //                   {
+  //                       "abilityName": "科学探索能力",
+  //                       "description": {
+  //                           "growth": "S1.在成人的帮助下,幼儿能够模仿其他学生的作品(例:运用自己喜欢的相似的材料)。",
+  //                           "behave": "看到其他小朋友在画上贴贴纸,他也会找喜欢的贴纸。",
+  //                           "behaviours": [],
+  //                           "example": "无",
+  //                           "tactics": "【环创】:\n1.创作机会和条件,支持幼儿自发的艺术表现和创造。\n2.为幼儿提供丰富的便于幼儿取放的材料、工具或物品。\n【师幼】:\n1.和幼儿一起发现并感受周边事物的特征。\n2.倾听幼儿的所见所想,鼓励幼儿大胆表达。",
+  //                           "education": "1.家长可以和幼儿一起玩串珠的游戏,家长示范,幼儿模仿。\n2.家长可以和幼儿一起进行阅读绘本,一起模仿绘本里动物或者人物的动作、声音和表情等。"
+  //                       },
+  //                       "descriptionExtra": {
+  //                           "growth": "",
+  //                           "behave": "2024年04月23日,在来来来,王凯昊、朱婷、陈树炎、李佩、雷晴、刘雯雯、孙艺洲、钱小豪、宝宝、田甜小朋友正在来来来活动。\n看到其他小朋友在画上贴贴纸,他也会找喜欢的贴纸。",
+  //                           "behaviours": [],
+  //                           "example": "",
+  //                           "tactics": "来来来12345",
+  //                           "education": ""
+  //                       }
+  //                   },
+  //                   {
+  //                       "abilityName": "超级无敌的学习能力",
+  //                       "description": {
+  //                           "growth": "S1.在成人的帮助下,幼儿能够模仿其他学生的作品(例:运用自己喜欢的相似的材料)。",
+  //                           "behave": "看到其他小朋友在画上贴贴纸,他也会找喜欢的贴纸。",
+  //                           "behaviours": [],
+  //                           "example": "无",
+  //                           "tactics": "【环创】:\n1.创作机会和条件,支持幼儿自发的艺术表现和创造。\n2.为幼儿提供丰富的便于幼儿取放的材料、工具或物品。\n【师幼】:\n1.和幼儿一起发现并感受周边事物的特征。\n2.倾听幼儿的所见所想,鼓励幼儿大胆表达。",
+  //                           "education": "1.家长可以和幼儿一起玩串珠的游戏,家长示范,幼儿模仿。\n2.家长可以和幼儿一起进行阅读绘本,一起模仿绘本里动物或者人物的动作、声音和表情等。"
+  //                       },
+  //                       "descriptionExtra": {
+  //                           "growth": "",
+  //                           "behave": "2024年04月23日,在来来来,王凯昊、朱婷、陈树炎、李佩、雷晴、刘雯雯、孙艺洲、钱小豪、宝宝、田甜小朋友正在来来来活动。\n看到其他小朋友在画上贴贴纸,他也会找喜欢的贴纸。",
+  //                           "behaviours": [],
+  //                           "example": "",
+  //                           "tactics": "来来来12345",
+  //                           "education": ""
+  //                       }
+  //                   },
+  //               ]
+  //           },
+  //       ]
+  // }
+});
+
+const isImages = computed(() => {
+  return singleRecord.value.story?.images.length !== 0;
+});
+const isVideo = computed(() => {
+  return singleRecord.value.story?.videos.length !== 0;
+});
+const isContent = computed(() => {
+  return singleRecord.value.story?.content && singleRecord.value.story?.content.length !== 0;
+});
+const isHas = computed(() => {
+  return singleRecord.value.story && (isImages.value || isVideo.value || isContent.value);
+});
+
+async function confirm() {
+  // console.log("confirm");
+  await joinSemesterReport(singleRecord.value.recordId, 0);
+  showConfirm.value = false;
+}
+
+function formatNames(names: string[]) {
+  let str = "";
+  names.map((name, nameIndex) => {
+    if (nameIndex !== 0) {
+      str += "、";
+    }
+    str += name;
+  });
+  return str;
+}
+
+function send() {
+  if (singleRecord.value.sendReport) return;
+  if (typeof babyId === "string" && typeof id === "string")
+    sendSingleRecord({
+      babyId,
+      id,
+      recordType: "1",
+    });
+}
+
+function changeJoinState() {
+  if (singleRecord.value.joinSemester === 1) {
+    showConfirm.value = true;
+  } else {
+    joinSemesterReport(singleRecord.value.recordId, 1);
+  }
+}
+
+function getStyle(index: number, length: number) {
+  return {
+    borderRadius: `${
+      length === 1 ? "17px" : index === 0 ? "17px 17px 0 0" : index === length - 1 ? "0 0 17px 17px" : "0"
+    }`,
+    borderTop: `${index === 0 ? "4px solid #b6dcff" : "none"}`,
+    borderBottom: `${index === length - 1 ? "4px solid #b6dcff" : "none"}`,
+    boxShadow: `${index === 0 ? "0 1px 6px 0 rgba(42, 105, 253, 0.16)" : "none"}`,
+  };
+}
+
+onMounted(async () => {
+  // await getTestTeachToken();
+
+  if (typeof babyId === "string" && typeof id === "string") await getSingleRecord(babyId, id);
+});
+
+/**
+ * 行为记录单次报告
+ *
+ * b: babyId
+ * i: recordId
+ *
+ * http://192.168.1.17:8989/#/customize/SingleReport?b=1304672468777553921&i=1716737882201264130
+ */
+</script>
+
 <style scoped lang="scss">
 .single-report {
   position: fixed;

+ 86 - 49
src/views/customize/components/HeaderPart.vue

@@ -28,12 +28,13 @@
         {{ singleRecord.joinSemester === 1 ? "已加入报告" : "加入报告" }}
       </div>
       <!-- 导出table 只有单次的行为记录才有 -->
-      <div v-if="props.exportTable"  class="header-modal-item flex-center">
+      <div v-if="props.exportWord"  class="header-modal-item flex-center" @click="exportWord">
         导出文件
       </div>
     </div>
   </div>
   <ShareModal :show="shareShow" @close="shareShow = false" />
+  <exprotWord ref="exprotWordDom" />
 </template>
 
 
@@ -44,63 +45,67 @@ import { useRouter } from "vue-router";
 import { computed, onUnmounted, ref, watch } from "vue";
 import { ISingleRecord } from "@/types/customize";
 import ShareModal from "@/views/customize/components/ShareModal.vue";
+import exprotWord from '@/views/test.vue'
+
 const customizeStore = useCustomizeStore();
 
+const exprotWordDom = ref()
+
 const router = useRouter();
 
-const props = defineProps(["isParent", "isMyRoute", 'exportTable']);
+const props = defineProps(["isParent", "isMyRoute", 'exportWord', 'record']);
 const emit = defineEmits(["send", "change"]);
 
 const isShow = ref(false);
 const shareShow = ref(false);
 
 const singleRecord = computed<ISingleRecord>(() => {
-  // return customizeStore.singleRecord;
-  return {
-        "recordId": "1782591626363899905",
-        "babyId": "1382262152722042882",
-        "sendReport": 0,
-        "recordDate": "2024-04-23",
-        "teacherName": "雷lEi\uD83C\uDF88",
-        "babyNames": [
-            "王凯昊"
-        ],
-        "joinSemester": 1,
-        "sceneName": "区域活动",
-        "story": {
-            "images": [
-                "https://app-resources-luojigou.luojigou.vip/3649ac71b59e4fb1b8c025928c70e37e"
-            ],
-            "videos": [],
-            "content": ""
-        },
-        "domainList": [
-            {
-                "domainName": "逻辑思维通用领域",
-                "abilityList": [
-                    {
-                        "abilityName": "批判思维能力",
-                        "description": {
-                            "growth": "S1.在成人的帮助下,幼儿能够模仿其他学生的作品(例:运用自己喜欢的相似的材料)。",
-                            "behave": "看到其他小朋友在画上贴贴纸,他也会找喜欢的贴纸。",
-                            "behaviours": [],
-                            "example": "无",
-                            "tactics": "【环创】:\n1.创作机会和条件,支持幼儿自发的艺术表现和创造。\n2.为幼儿提供丰富的便于幼儿取放的材料、工具或物品。\n【师幼】:\n1.和幼儿一起发现并感受周边事物的特征。\n2.倾听幼儿的所见所想,鼓励幼儿大胆表达。",
-                            "education": "1.家长可以和幼儿一起玩串珠的游戏,家长示范,幼儿模仿。\n2.家长可以和幼儿一起进行阅读绘本,一起模仿绘本里动物或者人物的动作、声音和表情等。"
-                        },
-                        "descriptionExtra": {
-                            "growth": "",
-                            "behave": "2024年04月23日,在来来来,王凯昊、朱婷、陈树炎、李佩、雷晴、刘雯雯、孙艺洲、钱小豪、宝宝、田甜小朋友正在来来来活动。\n看到其他小朋友在画上贴贴纸,他也会找喜欢的贴纸。",
-                            "behaviours": [],
-                            "example": "",
-                            "tactics": "来来来12345",
-                            "education": ""
-                        }
-                    }
-                ]
-            }
-        ]
-  }
+  return customizeStore.singleRecord;
+  // return {
+  //       "recordId": "1782591626363899905",
+  //       "babyId": "1382262152722042882",
+  //       "sendReport": 0,
+  //       "recordDate": "2024-04-23",
+  //       "teacherName": "雷lEi\uD83C\uDF88",
+  //       "babyNames": [
+  //           "王凯昊"
+  //       ],
+  //       "joinSemester": 1,
+  //       "sceneName": "区域活动",
+  //       "story": {
+  //           "images": [
+  //               "https://app-resources-luojigou.luojigou.vip/3649ac71b59e4fb1b8c025928c70e37e"
+  //           ],
+  //           "videos": ["https://app-resources-luojigou.luojigou.vip/3649ac71b59e4fb1b8c025928c70e37e"],
+  //           "content": ""
+  //       },
+  //       "domainList": [
+  //           {
+  //               "domainName": "逻辑思维通用领域",
+  //               "abilityList": [
+  //                   {
+  //                       "abilityName": "批判思维能力",
+  //                       "description": {
+  //                           "growth": "S1.在成人的帮助下,幼儿能够模仿其他学生的作品(例:运用自己喜欢的相似的材料)。",
+  //                           "behave": "看到其他小朋友在画上贴贴纸,他也会找喜欢的贴纸。",
+  //                           "behaviours": [],
+  //                           "example": "无",
+  //                           "tactics": "【环创】:\n1.创作机会和条件,支持幼儿自发的艺术表现和创造。\n2.为幼儿提供丰富的便于幼儿取放的材料、工具或物品。\n【师幼】:\n1.和幼儿一起发现并感受周边事物的特征。\n2.倾听幼儿的所见所想,鼓励幼儿大胆表达。",
+  //                           "education": "1.家长可以和幼儿一起玩串珠的游戏,家长示范,幼儿模仿。\n2.家长可以和幼儿一起进行阅读绘本,一起模仿绘本里动物或者人物的动作、声音和表情等。"
+  //                       },
+  //                       "descriptionExtra": {
+  //                           "growth": "",
+  //                           "behave": "2024年04月23日,在来来来,王凯昊、朱婷、陈树炎、李佩、雷晴、刘雯雯、孙艺洲、钱小豪、宝宝、田甜小朋友正在来来来活动。\n看到其他小朋友在画上贴贴纸,他也会找喜欢的贴纸。",
+  //                           "behaviours": [],
+  //                           "example": "",
+  //                           "tactics": "来来来12345",
+  //                           "education": ""
+  //                       }
+  //                   }
+  //               ]
+  //           }
+  //       ]
+  // }
 });
 
 const reportImg = computed(() => {
@@ -117,6 +122,36 @@ watch(isShow, (val) => {
   }
 });
 
+const exportWord = () => {
+  exprotWordDom.value.exportWordDocx({
+    ...props.record,
+    //@ts-ignore
+    babyNames: props.record.babyNames.map(item => ({name: item})),
+    //@ts-ignore
+    behaves: props.record.domainList.map(item => item.abilityList.map(_ => ({behave: _.descriptionExtra.behave}))).flat(Infinity),
+    //@ts-ignore
+    storyImages: props.record.story.images.map(item => ({url: item})),
+    storyContent: props.record.story.content,
+    //@ts-ignore
+    storyVideo: props.record.story.videos.length ? props.record.story.videos[0] : '',
+    //@ts-ignore
+    domainList: props.record.domainList.map(item => ({
+      domainName: item.domainName,
+      //@ts-ignore
+      abilityList: item.abilityList.map( (ability, index) => {
+        return {
+          abilityName: '·' + ability.abilityName + ` (s${index + 1})`,
+          growth: ability.description.growth,
+        }
+      })
+    })),
+    //@ts-ignore
+    tactics:  props.record.domainList.map(item => item.abilityList.map(_ => ({tactic: _.description.tactics}))).flat(Infinity),
+    //@ts-ignore
+    educations:  props.record.domainList.map(item => item.abilityList.map(_ => ({education: _.description.education}))).flat(Infinity)
+  }, props.record.babyNames[0] + '宝贝的观察评估')
+}
+
 function back() {
   props.isMyRoute === "1" ? router.go(-1) : returnAppPage();
 }
@@ -133,8 +168,10 @@ function handleClick() {
 
 
 function isWeChatBrowser() {
+  //@ts-ignore
   const userAgent = navigator.userAgent.toLowerCase();
-  return /MicroMessenger/i.test(userAgent);
+  // return !/MicroMessenger/i.test(userAgent);
+  return false;
 }
 
 // 调用函数进行判断

+ 125 - 125
src/views/customize/components/ShareModal.vue

@@ -1,125 +1,125 @@
-<script setup lang="ts">
-import { ActionSheet as VanActionSheet } from "vant";
-import { computed } from "vue";
-import { getImageUrl, shareToWechat } from "@/utils";
-import { useRoute } from "vue-router";
-
-const list: {
-  id: 0 | 1;
-  title: string;
-  imgUrl: string;
-}[] = [
-  {
-    id: 0,
-    title: "微信好友",
-    imgUrl: getImageUrl("weixin_friend"),
-  },
-  {
-    id: 1,
-    title: "朋友圈",
-    imgUrl: getImageUrl("circle_friend"),
-  },
-];
-
-const { title } = useRoute().meta;
-
-const emit = defineEmits(["close"]);
-
-const props = withDefaults(
-  defineProps<{
-    show: boolean;
-  }>(),
-  {
-    show: false,
-  },
-);
-
-const show = computed(() => props.show);
-
-function jump(id: 0 | 1) {
-  const image = getImageUrl("openAppLogo");
-  const description = "深度学习单词报告";
-  const url = window.location.href;
-
-  if (typeof title === "string") shareToWechat(image, title, id, description, url);
-}
-</script>
-
-<template>
-  <VanActionSheet :show="show" :closeable="false" @close="emit('close')">
-    <div class="share" @click.stop>
-      <div class="share-title">分享至</div>
-      <div class="share-list">
-        <div v-for="item in list" :key="item.id" class="share-list-item" @click="jump(item.id)">
-          <img :src="item.imgUrl" alt="" />
-          <div>{{ item.title }}</div>
-        </div>
-      </div>
-      <div class="share-cancel flex-center" @click="emit('close')">取消</div>
-    </div>
-  </VanActionSheet>
-</template>
-
-<style scoped lang="scss">
-.share {
-  //width: 375px;
-  height: 256px;
-  background: #ffffff;
-  //border-radius: 10px 10px 0 0;
-  overflow: hidden;
-
-  &-title {
-    margin: 20px auto 0;
-    height: 18px;
-    font-size: 14px;
-    font-family:
-      PingFang SC-Regular,
-      PingFang SC;
-    font-weight: 400;
-    color: #333333;
-    line-height: 18px;
-    text-align: center;
-  }
-
-  &-list {
-    display: flex;
-    justify-content: space-evenly;
-    align-items: center;
-
-    &-item {
-      padding-top: 20px;
-      padding-bottom: 20px;
-
-      div {
-        font-size: 12px;
-        font-family:
-          PingFang SC-Regular,
-          PingFang SC;
-        font-weight: 400;
-        color: #333333;
-        text-align: center;
-      }
-
-      img {
-        margin-bottom: 6px;
-        width: 48px;
-        height: 48px;
-      }
-    }
-  }
-
-  &-cancel {
-    margin: 20px auto 0;
-    width: 343px;
-    height: 46px;
-    border-radius: 40px 40px 40px 40px;
-    border: 1px solid #e2e2e2;
-    font-size: 14px;
-    font-family:
-      PingFang SC-Regular,
-      PingFang SC;
-    font-weight: 400;
-    color: #333333;
-  }
-}
-</style>
+<script setup lang="ts">
+import { ActionSheet as VanActionSheet } from "vant";
+import { computed } from "vue";
+import { getImageUrl, shareToWechat } from "@/utils";
+import { useRoute } from "vue-router";
+
+const list: {
+  id: 0 | 1;
+  title: string;
+  imgUrl: string;
+}[] = [
+  {
+    id: 0,
+    title: "微信好友",
+    imgUrl: getImageUrl("weixin_friend"),
+  },
+  {
+    id: 1,
+    title: "朋友圈",
+    imgUrl: getImageUrl("circle_friend"),
+  },
+];
+
+const { title } = useRoute().meta;
+
+const emit = defineEmits(["close"]);
+
+const props = withDefaults(
+  defineProps<{
+    show: boolean;
+  }>(),
+  {
+    show: false,
+  },
+);
+
+const show = computed(() => props.show);
+
+function jump(id: 0 | 1) {
+  const image = getImageUrl("openAppLogo");
+  const description = "幼儿行为智慧观察评价";
+  const url = window.location.href;
+
+  if (typeof title === "string") shareToWechat(image, title, id, description, url);
+}
+</script>
+
+<template>
+  <VanActionSheet :show="show" :closeable="false" @close="emit('close')">
+    <div class="share" @click.stop>
+      <div class="share-title">分享至</div>
+      <div class="share-list">
+        <div v-for="item in list" :key="item.id" class="share-list-item" @click="jump(item.id)">
+          <img :src="item.imgUrl" alt="" />
+          <div>{{ item.title }}</div>
+        </div>
+      </div>
+      <div class="share-cancel flex-center" @click="emit('close')">取消</div>
+    </div>
+  </VanActionSheet>
+</template>
+
+<style scoped lang="scss">
+.share {
+  //width: 375px;
+  height: 256px;
+  background: #ffffff;
+  //border-radius: 10px 10px 0 0;
+  overflow: hidden;
+
+  &-title {
+    margin: 20px auto 0;
+    height: 18px;
+    font-size: 14px;
+    font-family:
+      PingFang SC-Regular,
+      PingFang SC;
+    font-weight: 400;
+    color: #333333;
+    line-height: 18px;
+    text-align: center;
+  }
+
+  &-list {
+    display: flex;
+    justify-content: space-evenly;
+    align-items: center;
+
+    &-item {
+      padding-top: 20px;
+      padding-bottom: 20px;
+
+      div {
+        font-size: 12px;
+        font-family:
+          PingFang SC-Regular,
+          PingFang SC;
+        font-weight: 400;
+        color: #333333;
+        text-align: center;
+      }
+
+      img {
+        margin-bottom: 6px;
+        width: 48px;
+        height: 48px;
+      }
+    }
+  }
+
+  &-cancel {
+    margin: 20px auto 0;
+    width: 343px;
+    height: 46px;
+    border-radius: 40px 40px 40px 40px;
+    border: 1px solid #e2e2e2;
+    font-size: 14px;
+    font-family:
+      PingFang SC-Regular,
+      PingFang SC;
+    font-weight: 400;
+    color: #333333;
+  }
+}
+</style>

BIN
src/views/termplate.docx


+ 144 - 0
src/views/test.vue

@@ -0,0 +1,144 @@
+<template>
+<div ref="file"></div>
+</template>
+
+<script lang="ts" setup >
+import {  ref } from 'vue';
+import PizZip from 'pizzip';
+import Docxtemplater from 'docxtemplater';
+//@ts-ignore
+import { saveAs } from 'file-saver';
+//@ts-ignore
+import JSZipUtils from 'jszip-utils'
+//@ts-ignore
+import { renderAsync } from 'docx-preview'
+//@ts-ignore
+import ImageModule  from 'docxtemplater-image-module-free'
+//@ts-ignore
+import QRCode from 'qrcode'
+
+const termplateDocxPath = new URL('./termplate.docx', import.meta.url).href
+
+let file = ref(null);
+
+const  exportWordDocx = async  (tempDocxPath: any, dataObj: any, fileName: any) =>  {
+  console.log('dataObj:', dataObj);
+
+  const storyVideos = dataObj.storyVideo ? [{url: await generateQRCode(dataObj.storyVideo), desc: '(扫码查看视频)'}] : []
+ 
+    // 读取并获得模板文件的二进制内容
+     JSZipUtils.getBinaryContent (tempDocxPath, async (error: any, content: any) => {
+        if (error) {
+          throw error
+        }
+
+        let opts = {
+          centered: false,
+          getImage: function (tagValue: any) {
+            return new Promise(function (resolve, reject) {
+              // if (!tagValue) 
+              console.log('tagValue:', tagValue);
+              
+                JSZipUtils.getBinaryContent(tagValue, function (error: any, content: any) {
+                    if (error) {
+                        return reject(error);
+                    }
+                    return resolve(content);
+                });
+            });
+          },
+          // img: any, tagValue, tagName
+          getSize: () => [150, 150]
+        };
+
+        let imageModule = new ImageModule(opts);
+
+        const zip = new PizZip(content)
+        const doc = new Docxtemplater().loadZip(zip).attachModule(imageModule).compile();
+        
+        doc.resolveData( {...dataObj, storyVideos} ).then(() => {
+            doc.render()
+            const out = doc.getZip().generate({
+                type: 'blob',
+                mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
+            })
+            saveAs(out, fileName)
+        }).catch(err => console.log('errorsss', err))
+    })
+}
+
+//要填充的数据
+//通常请求后端接口获得
+const dataObj = {
+  // name: '探测器',
+  // id: '002',
+  // imgList: [
+  //   {
+  //     img: 'https://images.pexels.com/photos/3291250/pexels-photo-3291250.jpeg?auto=compress&cs=tinysrgb&w=1600&lazy=load',
+
+  //   },
+  //   {
+  //     img: 'https://images.pexels.com/photos/3291250/pexels-photo-3291250.jpeg?auto=compress&cs=tinysrgb&w=1600&lazy=load'
+  //   },
+  //   {
+  //     img: 'https://images.pexels.com/photos/3291250/pexels-photo-3291250.jpeg?auto=compress&cs=tinysrgb&w=1600&lazy=load'
+  //   },
+  //   {
+  //     img: 'https://images.pexels.com/photos/3291250/pexels-photo-3291250.jpeg?auto=compress&cs=tinysrgb&w=1600&lazy=load'
+  //   },
+  //   {
+  //     img: 'https://images.pexels.com/photos/3291250/pexels-photo-3291250.jpeg?auto=compress&cs=tinysrgb&w=1600&lazy=load'
+  //   },
+  // ],
+  // clazz: '小一班',
+  // teacherName: '雷晴天',
+  // observe: `我打江南走过,那等在季节里的容颜如莲花的开落,东风不来, 三月的柳絮不飞, 你的心如小小寂寞的城, 恰若青石的街道向晚跫音不响, 三月的春帷不揭, 你的心是小小的窗扉紧掩, 我达达的马蹄是美丽的错误, 我不是归人,是个过客……`,
+  data: [
+      {position:'-15', average: '-15.3', difference: '-0.3'},
+      {position:'0', average: '0.2', difference: '0.2'},
+      {position:'15', average: '15.1', difference: '0.1'},
+  ]
+};
+
+//文件名称
+// const fileName = `示例文件.docx`;
+
+// exportWordDocx(
+//   aa,
+//   dataObj,
+//   fileName,
+//   (file)=>{
+//     //预览功能
+//     renderAsync(file, file.value);
+//     //若要实现文档下载,可直接用下面这行代码
+//     saveAs(file, fileName);
+//     //若要先预览,点击“下载”按钮之后再下载文档,
+//     //可将file先暂存在this.file中,响应按钮事件后调用saveAs(this.file, fileName)
+//     //this.file = file;
+// });
+
+
+const generateQRCode = (content: string) => {
+  return new Promise((resolve, reject) => {
+    const qrcodeContent  = content
+    QRCode.toDataURL(qrcodeContent, (error: any, dataUrl: any) => {
+      if (error) reject(error)
+      else resolve(dataUrl)
+    })
+  })
+}
+
+// , (file)=>{
+//     //预览功能
+//     renderAsync(file, file.value);
+//     //若要实现文档下载,可直接用下面这行代码
+//     saveAs(file, fileName);
+//     //若要先预览,点击“下载”按钮之后再下载文档,
+//     //可将file先暂存在this.file中,响应按钮事件后调用saveAs(this.file, fileName)
+//     //this.file = file;
+//   }
+defineExpose({
+  exportWordDocx: (record: any, fileName: string) => exportWordDocx(termplateDocxPath, {...dataObj, ...record}, fileName)
+})
+
+</script>

+ 30 - 30
tsconfig.json

@@ -1,30 +1,30 @@
-{
-  "compilerOptions": {
-    "target": "ES2020",
-    "useDefineForClassFields": true,
-    "module": "ESNext",
-    "lib": ["ES2020", "DOM", "DOM.Iterable"],
-    "skipLibCheck": true,
-    "baseUrl": ".",
-    "paths": {
-      "@/*": [
-        "src/*"
-      ]
-    },
-    "noEmitOnError": true,
-    /* Bundler mode */
-    "moduleResolution": "bundler",
-    "resolveJsonModule": true,
-    "isolatedModules": true,
-    "noEmit": true,
-    "jsx": "preserve",
-    /* Linting */
-    "strict": true,
-    "noUnusedLocals": true,
-    "noUnusedParameters": true,
-    "noFallthroughCasesInSwitch": true,
-    "allowSyntheticDefaultImports": true
-  },
-  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "src/utils/exportPdf.js"],
-  "references": [{ "path": "./tsconfig.node.json" }]
-}
+{
+  "compilerOptions": {
+    "target": "ES2021",
+    "useDefineForClassFields": true,
+    "module": "ESNext",
+    "lib": ["ES2021", "DOM", "DOM.Iterable"],
+    "skipLibCheck": true,
+    "baseUrl": ".",
+    "paths": {
+      "@/*": [
+        "src/*"
+      ]
+    },
+    "noEmitOnError": true,
+    /* Bundler mode */
+    "moduleResolution": "bundler",
+    "resolveJsonModule": true,
+    "isolatedModules": true,
+    "noEmit": true,
+    "jsx": "preserve",
+    /* Linting */
+    "strict": true,
+    "noUnusedLocals": true,
+    "noUnusedParameters": true,
+    "noFallthroughCasesInSwitch": true,
+    "allowSyntheticDefaultImports": true
+  },
+  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "src/utils/exportPdf.js"],
+  "references": [{ "path": "./tsconfig.node.json" }]
+}

+ 2 - 0
vite.config.ts

@@ -3,6 +3,7 @@ import vue from "@vitejs/plugin-vue";
 import { resolve } from "path";
 
 import legacy from '@vitejs/plugin-legacy'
+import { terser } from 'rollup-plugin-terser';
 // https://vitejs.dev/config/
 
 const pathResolve = (dir: string): string => {
@@ -39,6 +40,7 @@ export default defineConfig({
         'esnext.string.match-all'
       ]
     }),
+    terser()
   ],
   base: "./",
   css: {

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