Browse Source

意见反馈

黄琪勇 8 months ago
parent
commit
9e54a5f298

+ 30 - 0
package-lock.json

@@ -12,6 +12,7 @@
         "@varlet/ui": "^2.9.5",
         "clean-deep": "^3.4.0",
         "consola": "^2.15.3",
+        "cos-js-sdk-v5": "^1.4.20",
         "dayjs": "^1.11.7",
         "eventemitter3": "^5.0.0",
         "hammerjs": "^2.0.8",
@@ -2752,6 +2753,14 @@
       "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.47.tgz",
       "integrity": "sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ=="
     },
+    "node_modules/@xmldom/xmldom": {
+      "version": "0.8.10",
+      "resolved": "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
+      "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==",
+      "engines": {
+        "node": ">=10.0.0"
+      }
+    },
     "node_modules/acorn": {
       "version": "8.8.2",
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
@@ -3210,6 +3219,14 @@
         "url": "https://opencollective.com/core-js"
       }
     },
+    "node_modules/cos-js-sdk-v5": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmmirror.com/cos-js-sdk-v5/-/cos-js-sdk-v5-1.8.1.tgz",
+      "integrity": "sha512-crsdrcDSRJqdniG61JiQCEA4FIUMuvqNohRCIONrHGd23xvykZs8uySLHmvAKDieAYRT+P72F/Ng7kjycW2b1w==",
+      "dependencies": {
+        "@xmldom/xmldom": "^0.8.6"
+      }
+    },
     "node_modules/css-line-break": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",
@@ -7549,6 +7566,11 @@
       "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.47.tgz",
       "integrity": "sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ=="
     },
+    "@xmldom/xmldom": {
+      "version": "0.8.10",
+      "resolved": "https://registry.npmmirror.com/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
+      "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw=="
+    },
     "acorn": {
       "version": "8.8.2",
       "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz",
@@ -7897,6 +7919,14 @@
         "browserslist": "^4.21.5"
       }
     },
+    "cos-js-sdk-v5": {
+      "version": "1.8.1",
+      "resolved": "https://registry.npmmirror.com/cos-js-sdk-v5/-/cos-js-sdk-v5-1.8.1.tgz",
+      "integrity": "sha512-crsdrcDSRJqdniG61JiQCEA4FIUMuvqNohRCIONrHGd23xvykZs8uySLHmvAKDieAYRT+P72F/Ng7kjycW2b1w==",
+      "requires": {
+        "@xmldom/xmldom": "^0.8.6"
+      }
+    },
     "css-line-break": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz",

+ 1 - 0
package.json

@@ -12,6 +12,7 @@
     "@varlet/ui": "^2.9.5",
     "clean-deep": "^3.4.0",
     "consola": "^2.15.3",
+    "cos-js-sdk-v5": "^1.4.20",
     "dayjs": "^1.11.7",
     "eventemitter3": "^5.0.0",
     "hammerjs": "^2.0.8",

+ 68 - 0
src/helpers/oss-file-upload.ts

@@ -0,0 +1,68 @@
+import request from "../utils/request"
+import COS from "cos-js-sdk-v5"
+
+const tencentBucket = "daya-online-1303457149"
+const ossType = "tencent"
+
+export async function fileUpload(fileName:string,file:Blob) {
+   const { data } = await getUploadSign(fileName)
+   return await onOnlyFileUpload(data.signature, {
+      fileName,
+      file
+   })
+}
+
+const getUploadSign = async (fileName:string) => {
+   const fileUrl = "yjl/" + fileName
+   return request.post("/open/getUploadSign", {
+      data: {
+         postData: {
+            key: fileUrl
+         },
+         pluginName: ossType,
+         bucketName: tencentBucket,
+         filename: fileUrl
+      },
+      requestType: "json",
+      params: { pluginName: ossType }
+   })
+}
+
+const onOnlyFileUpload = async (signature: string, params: { fileName: string; file: Blob }) => {
+   let file = ""
+   let errorObj: any = null
+   const cos = new COS({
+      Domain: "https://oss.dayaedu.com",
+      Protocol: "https",
+      getAuthorization: async (options, callback: any) => {
+         callback({ Authorization: signature })
+      }
+   })
+   await cos
+      .uploadFile({
+         Bucket: tencentBucket /* 填写自己的 bucket,必须字段 */,
+         Region: "ap-nanjing" /* 存储桶所在地域,必须字段 */,
+         Key: `yjl/${params.fileName}`,
+         /* 存储在桶里的对象键(例如:1.jpg,a/b/test.txt,图片.jpg)支持中文,必须字段 */
+         Body: params.file, // 上传文件对象
+         SliceSize: 1024 * 1024 * 500 /* 触发分块上传的阈值,超过5MB使用分块上传,小于5MB使用简单上传。可自行设置,非必须 */,
+         onProgress: function (progressData) {
+            // onProgress({ percent: Math.ceil((progressData.percent || 0) * 100) })
+         }
+      })
+      .then((res: any) => {
+         if (res.Location?.indexOf("http") >= 0) {
+            file = res.Location
+         } else {
+            file = "https://" + res.Location
+         }
+      })
+      .catch((error: any) => {
+         errorObj = error
+      })
+   if (file) {
+      return file
+   } else {
+      throw new Error(errorObj)
+   }
+}

+ 186 - 101
src/page-instrument/custom-plugins/helper-model/recommendation/index.module.less

@@ -1,112 +1,197 @@
-
-.closeBtn {
-    position: absolute;
-    right: -34px;
-    top: -6px;
-    width: 24px;
-    height: 24px;
-    border-radius: 50%;
-    background-color: #fff;
-    overflow: hidden;
-    padding: 6px;
-
-    img {
-        width: 100%;
-        height: 100%;
-        display: block;
-    }
-
-    &:active {
-        opacity: .8;
-    }
-}
-
-.content {
-    position: relative;
-    border-radius: 8px;
-    width: 300px;
-    // height: 90vh;
-    max-height: 370px;
-    background-color: #fff;
-    --van-tabs-line-height: 42px;
-    overflow: hidden;
-    :global {
-        .van-tabs__wrap{
-            border-bottom: 1Px solid #F0F0F0;
+.recommendation{
+    .head{
+        background: url("/src/page-instrument/header-top/image/headImg.png") no-repeat;
+        background-size: 100% 100%;
+        width: 372px;
+        height: 57px;
+        position: relative;
+        .headTit{
+            position: absolute;
+            bottom: 8px;
+            left: 50%;
+            transform: translateX(-50%);
+            width: 76px;
+            height: 20px;
+        }        
+        .closeImg{
+            position: absolute;
+            top: 0;
+            right: -38px;
+            width: 32px;
+            height: 32px;
+            cursor: pointer;
         }
-        .van-tabs__content {
-            max-height: calc(90vh - var(--van-tabs-line-height));
+    }
+    .content{
+        width: 354px;
+        height: 284px;
+        background: #B0D8FF;
+        box-shadow: 0px 4px 0px 0px #7AAEE0;
+        border-radius: 0px 0px 24px 24px;
+        margin: 0 auto;
+        padding: 10px;
+        .conBox{
+            width: 100%;
+            height: 100%;
+            background: #EAF2FB;
+            border-radius: 12px;
             overflow-y: auto;
-
+            padding: 10px;
             &::-webkit-scrollbar {
                 width: 0;
                 display: none;
             }
-        }
-        .van-field{
-            font-size: 12Px;
-            line-height: 16Px;
-        }
-        .van-field__value{
-            background-color: #F8F8F8;
-            padding: 9px;
-        }
-    }
-    &.pcContent {
-        :global {
-            .van-field{
-                font-size: 16Px;
-                line-height: 20Px;
+            .dropdownMenu{
+                width: 138px;
+                position: relative;
+                :global{
+                    .van-dropdown-menu__bar{
+                        height: 30px;
+                        background: #FFFFFF;
+                        border-radius: 15px;
+                        .van-dropdown-menu__item{
+                            padding: 0 12px;
+                        }
+                        .van-dropdown-menu__title{
+                            --van-gray-4: #AAAAAA;
+                            font-weight: 500;
+                            font-size: 14px;
+                            color: #AAAAAA;
+                            padding: 0 10px 0 0;
+                            &::after{
+                                right: 0;
+                                opacity: initial;
+                            }
+                        }
+                    }
+                    .recommendationDropdownItem{
+                        top: 112px !important;
+                        left: 24px;
+                        width: 152px;
+                        .van-dropdown-item__content{
+                            max-height:162px;
+                            padding: 0 10px;
+                            background: #FFFFFF;
+                            box-shadow: 0px 2px 10px 0px rgba(0,0,0,0.1);
+                            border-radius: 12px;
+                            .van-cell{
+                                margin-top: 6px;
+                                padding: 0;
+                                font-weight: 400;
+                                font-size: 13px;
+                                color: #777777;
+                                line-height: 32px;
+                                text-align: center;
+                                &::after{
+                                    border: none;
+                                }
+                                &:last-child{
+                                    margin-bottom: 6px;
+                                }
+                                &.van-dropdown-item__option--active{
+                                    background: #F2FAFF;
+                                    border-radius: 8px;
+                                    color: #1CACF1;
+                                }
+                                .van-cell__value{
+                                    display: none;
+                                }
+                            }
+                        }
+                    }
+                }
+                &.currItem{
+                    :global{
+                        .van-dropdown-menu__bar  .van-dropdown-menu__title{
+                            color: #1CACF1;
+                            --van-gray-4:#1CACF1;
+                        }
+                    }
+                }
             }
-            .van-cell__title {
-                font-size: 18Px;
+            .field{
+                margin-top: 10px;
+                height: 80px;
+                background: #FFFFFF;
+                border-radius: 12px;
+                padding: 10px;
+                :global{
+                    .van-field__control{
+                        font-weight: 500;
+                        font-size: 14px;
+                        color: #131415;
+                        &::placeholder {
+                            font-weight: 400;
+                            font-size: 14px;
+                            color: #AAAAAA;
+                        }
+                    }
+                }
+            }
+            .uploader{
+                margin-top: 10px;
+                display: block;
+                :global{
+                    .van-uploader__preview{
+                        margin: 0 8px 0 0;
+                        .van-uploader__preview-image{
+                            width: 56px;
+                            height: 56px;
+                            border-radius: 6px;
+                        }
+                        .van-uploader__preview-delete--shadow{
+                            width: 14px;
+                            height: 14px;
+                            border-radius: 50%;
+                            right: 3px;
+                            top: 3px;
+                            background:rgba(0,0,0,0.4);
+                            display: flex;
+                            justify-content: center;
+                            align-items: center;
+                            .van-uploader__preview-delete-icon{
+                                transform:initial;
+                                position:initial;
+                                font-size: 12px;
+                            }
+                        }
+                    }
+                }
+                .uploaderbox{
+                    width: 56px;
+                    height: 56px;
+                    background: #FFFFFF;
+                    border-radius: 6px;
+                    border: 1px dashed #D9D9D9;
+                    display: flex;
+                    flex-direction: column;
+                    justify-content: center;
+                    align-items: center;
+                    .img{
+                        width: 22px;
+                        height: 22px;
+                        margin-bottom: 2px;
+                    }
+                    >div{
+                        font-weight: 400;
+                        font-size: 10px;
+                        color: #777777;
+                    }
+                }
+            }
+            .btnCon{
+                margin-top: 16px;
+                display: flex;
+                justify-content: center;
+                .img{
+                    cursor: pointer;
+                    width: 118px;
+                    height: 39px;
+                    &:first-child{
+                        margin-right: 20px;
+                    }
+                }
             }
-        }
-    }
-}
-
-.tags {
-    display: flex;
-    text-align: center;
-    flex-wrap: wrap;
-    padding: 0 var(--van-cell-horizontal-padding) var(--van-cell-vertical-padding) var(--van-cell-horizontal-padding);
-    >span {
-        margin: 3px 12px 3px 0;
-        border-radius: 3PX;
-        display: block;
-        width: 30%;
-        font-size: 12PX;
-        padding: 6PX 0;
-        background-color: #F8F8F8;
-        color: #999999;
-        border: 1PX solid #F8F8F8;
-        &:nth-child(3n+3) {
-            margin-right: 0px;
-        }
-        &.active {
-            color: var(--van-primary-color);
-            border-color: var(--van-primary-color);
-            background-color: #ECF9FF;
-            pointer-events: none;
-        }
-    }
-    &.pcTags {
-        >span {
-            font-size: 15PX;
-        }
-    }
-}
-.btn{
-    display: block;
-    height: 36px;
-    font-size: 13px;
-    margin: 0 auto;
-}
-
-.pcContent {
-    .tags {
-        >span {
-            font-size: 16PX;
         }
     }
 }

+ 85 - 40
src/page-instrument/custom-plugins/helper-model/recommendation/index.tsx

@@ -1,29 +1,33 @@
-import { defineComponent, reactive, ref, onMounted } from "vue";
+import { defineComponent, reactive, ref, onMounted, computed } from "vue";
 import styles from "./index.module.less";
-import iconClose from "../icons/close2.svg";
-import { Button, Cell, Field, Tab, Tabs, showToast } from "vant";
-import iconSubmit from "../icons/icon-submit.svg";
+import { Button, Cell, Field, Tab, Tabs, showToast, DropdownMenu, DropdownItem, Uploader } from "vant";
 import { sysSuggestionAdd, getSuggestionList } from "/src/page-instrument/api";
 import { storeData } from "/src/store";
 import state, { IPlatform } from "/src/state";
+import { headImg } from "/src/page-instrument/header-top/image";
+import { fileUpload } from "/src/helpers/oss-file-upload"
 
 export default defineComponent({
 	name: "recommendation",
 	emits: ["close"],
 	setup(props, { emit }) {
-		const suggestionTypeList = ref([] as any);
-		const tags = ["识别不准", "无法评测", "不出评测结果", "曲谱不一致", "指法错误", "其他"];
+		const suggestionTypeList = ref<any[]>([]);
 		const recommenData = reactive({
-			loading: false,
-			active: "识别不准",
 			message: "",
-			suggestId: null,
+			suggestId: "",
 		});
+		const fileList = ref<any[]>([])  // 上传列表
 		// 获取建议类别
 		const getTypeList = async () => {
 			try {
-				const res = await getSuggestionList({ rows: 9999, page: 1 });
-				suggestionTypeList.value = res.data.rows || [];
+				const res = await getSuggestionList({ rows: 9999, page: 1, useClient: "SMART_PRACTICE" });
+				const data = res.data.rows || [];
+				suggestionTypeList.value = data.map((item:any) => {
+					return {
+						text: item.name,
+						value: item.id
+					}
+				})
 			} catch (e) {
 				//
 			}
@@ -32,65 +36,106 @@ export default defineComponent({
 		/** 提交意见反馈 */
 		const handleSubmit = async () => {
 			if (!recommenData.message || !recommenData.suggestId) {
-				const desc = !recommenData.suggestId ? '请先选择问题类型' : '请先填写意见反馈'
+				const desc = !recommenData.suggestId ? '请先选择反馈类型' : '请先填写意见反馈'
 				showToast({
 					message: desc,
 					position: "top",
 				});
 				return;
 			}
-			recommenData.loading = true;
 			try {
+				const attachmentUrlsArr = fileList.value.reduce((arr,item)=>{
+					item.url && arr.push(item.url)
+					return arr
+				},[])
 				await sysSuggestionAdd({
 					content: recommenData.message,
 					type: "SMART_PRACTICE",
 					suggestionTypeId: recommenData.suggestId,
-					mobileNo: storeData.user?.phone
+					mobileNo: storeData.user?.phone,
+					attachmentUrls: attachmentUrlsArr.join(",")
 				});
 				showToast({
 					message: "意见反馈已提交",
 					position: "top",
 				});
 				emit("close");
-				recommenData.suggestId = null
+				recommenData.suggestId = ""
 				recommenData.message = ''
+				fileList.value = []
 			} catch (error) {}
-			recommenData.loading = false;
+		};
+		const currItem = computed(()=>{
+			const currItem = suggestionTypeList.value.find((item:any)=>{
+				return item.value === recommenData.suggestId
+			})
+			return currItem
+		})
+		/* 文件上传 */
+		const afterRead = (file:any[]|Record<string,any>) => {
+			let files:any[] = []
+			if(Array.isArray(file)){
+				files = file.map(item =>{
+							item.status = 'uploading';
+							item.message = '上传中...';
+							item.key = new Date().getTime() + item.file.name
+							return item
+						})
+			} else {
+				file.status = 'uploading';
+				file.message = '上传中...';
+				file.key = new Date().getTime() + file.file.name
+				files.push(file)
+			}
+			files.map(async item=>{
+				try {
+					const url = await fileUpload(item.key,item.file)
+					item.status = ""
+					item.url = url
+				}catch{
+					item.status = "failed"
+					item.message = "上传失败"
+				}
+			})
+		};
+		const maxSize = 5
+		const onOversize = () => {
+			showToast(`文件大小不能超过 ${maxSize}M`);
 		};
 		onMounted(() => {
 			getTypeList();
 		});
 		return () => (
-			<div class={[styles.content, state.platform === IPlatform.PC && styles.pcContent]}>
-				{ state.platform === IPlatform.PC && <div class={'top_drag'}></div> }
-				<Tabs lineHeight={0} color="#1A1A1A">
-					<Tab title="意见反馈">
-						<Cell border={false} title="请选择问题类型" />
-						<div class={styles.tags}>
-							{suggestionTypeList.value.map((item: any) => (
-								<span
-									class={[styles.tag, recommenData.suggestId === item.id && styles.active]}
-									onClick={() => (recommenData.suggestId = item.id)}
-								>
-									{item.name}
-								</span>
-							))}
-						</div>
+			<div class={[styles.recommendation]}>
+				<div class={styles.head}>
+					<img class={styles.headTit} src={headImg("recommendationName.png")} />
+					<img class={styles.closeImg} src={headImg("closeImg.png")} onClick={()=>{ emit("close") }} />
+				</div>
+				<div class={styles.content}>
+                    <div class={styles.conBox}>
+						<DropdownMenu class={[styles.dropdownMenu, currItem.value && styles.currItem]} overlay={false}>
+							<DropdownItem class={['recommendationDropdownItem']} title={ currItem.value?currItem.value.name:"请选择反馈类型"} v-model={recommenData.suggestId} options={suggestionTypeList.value}/>
+						</DropdownMenu>
 						<Field
+							class={styles.field}
 							v-model={recommenData.message}
-							rows="2"
-							autosize={{ maxHeight: 44 }}
 							border={false}
 							type="textarea"
 							maxlength={200}
-							placeholder="请详细描述您遇到的问题,以便我们尽快为您解决!"
-							show-word-limit
+							placeholder="请详细描述您遇到的问题,以便我们尽快为您解决"
 						/>
-						<Cell>
-							<img class={styles.btn} src={iconSubmit} onClick={handleSubmit} />
-						</Cell>
-					</Tab>
-				</Tabs>
+						<Uploader accept=".jpg,jpeg,.png" class={styles.uploader} max-size={maxSize * 1024 * 1024} onOversize={onOversize} v-model={fileList.value} after-read={afterRead} multiple max-count={4}>
+							<div class={styles.uploaderbox}>
+								<img class={styles.img} src={headImg("photo.png")}></img>
+								<div>上传图片</div>
+							</div>
+						</Uploader>
+						<div class={styles.btnCon}>
+							<img class={styles.img} src={headImg("qx.png")} onClick={()=>{ emit("close") }}></img>
+							<img class={styles.img} src={headImg("tj.png")} onClick={handleSubmit}></img>
+						</div>
+                    </div>  
+                </div>
 			</div>
 		);
 	},

BIN
src/page-instrument/header-top/image/photo.png


BIN
src/page-instrument/header-top/image/qx.png


BIN
src/page-instrument/header-top/image/recommendationName.png


BIN
src/page-instrument/header-top/image/tj.png


+ 21 - 3
src/page-instrument/header-top/settting/index.tsx

@@ -1,14 +1,19 @@
-import { defineComponent } from "vue";
+import { defineComponent, reactive } from "vue";
 import styles from "./index.module.less"
 import { headImg } from "../image";
 import { headTopData } from "../index"
-import { Switch, showToast, Field } from "vant";
+import { Switch, showToast, Field, Popup } from "vant";
 import state from "/src/state"
 import { smoothAnimationState} from "/src/page-instrument/view-detail/smoothAnimation"
+import Recommendation from "../../custom-plugins/helper-model/recommendation";
 
 export default defineComponent({
 	name: "settting",
 	setup() {
+        const helperData = reactive({
+			screenModelShow: false, // 投屏帮助
+			recommendationShow: false, // 建议
+		});
         // 加减评测频率
 		const operateHz = (type: number) => {
 			const minFrequency = state.baseFrequency - 10, maxFrequency = state.baseFrequency + 10
@@ -84,10 +89,23 @@ export default defineComponent({
                         </div>
                         <div class={styles.cellBtnBox}>
                             <img  src={headImg("tpbz.png")} />
-                            <img  src={headImg("yjfk.png")} />
+                            <img  src={headImg("yjfk.png")} onClick={() => (helperData.recommendationShow = true)} />
                         </div>
                     </div>  
                 </div>
+                <Popup
+					v-model:show={helperData.recommendationShow}
+					class="popup-custom van-scale center-closeBtn recommenBoxClass_drag"
+					transition="van-scale"
+					teleport="body"
+                    overlay-style={{background:'rgba(0, 0, 0, 0.3)'}}
+				>
+                    <Recommendation
+                        onClose={() => {
+                            helperData.recommendationShow = false;
+                        }}
+                    />
+				</Popup>
 			</div>
 		);
 	},