lsc il y a 1 mois
Parent
commit
3f7c41a8d4
5 fichiers modifiés avec 386 ajouts et 49 suppressions
  1. 289 48
      src/components/pages/pptEasy/addCourse3.vue
  2. 14 0
      src/lang/cn.json
  3. 14 0
      src/lang/en.json
  4. 14 0
      src/lang/hk.json
  5. 55 1
      src/mixins/mixin.js

+ 289 - 48
src/components/pages/pptEasy/addCourse3.vue

@@ -414,30 +414,39 @@
         <el-button type="primary" @click="choosePicVisible = false">{{ lang.ssConfirm }}</el-button>
       </span>
     </el-dialog>
-    <el-dialog :title="lang.SelectWebImage" :visible.sync="sysPicVisible2" :append-to-body="true" width="710px"
-      :before-close="handleClose" class="dialog_diy">
-      <div>
-        <div class="people_top_right" style="display: flex;align-items: center;">
-          <div style="position: relative; width: 100%;">
-            <el-input style="height: 100%" :placeholder="lang.ssSearchImageKey" v-model="searchImageValue"
-              @keyup.enter.native="resetImage()"></el-input>
-            <div class="search_img" @click="resetImage" style="right: 10px;">
-              <img src="../../../assets/icon/search.png" alt />
-            </div>
-          </div>
-          <el-button type="primary" size="default" style="margin-left: 10px;" @click="changePicture">{{ lang.ssChangeGrp
-          }}</el-button>
+    <div class="web-search-modal" v-if="sysPicVisible2">
+      <div class="modal-overlay"></div>
+      <div class="modal-content web-search-content">
+        <div class="modal-header">
+          <h3 class="modal-title">{{ lang.ssWebSearchImage }}</h3>
+          <div class="modal-close" @click="sysPicVisible2 = false">×</div>
         </div>
-        <div class="sysPicBox" v-loading="imageloading">
-          <div class="picNone" v-if="!imageList.length">
-            {{ lang.ssEnterKeywordSearch }}
+        <div class="modal-body web-search-body">
+          <div class="search-section">
+            <label class="search-label">{{ lang.ssSearchKeyword }}</label>
+            <div style="position: relative; width: 100%;">
+              <el-input class="search_input" style="height: 40px; border-radius: 4px;" :placeholder="lang.ssSearchImageKey" v-model="searchImageValue"
+                @keyup.enter.native="resetImage()"></el-input>
+              <div class="search_img" @click="resetImage" style="right: 10px;">
+                <img src="../../../assets/icon/search.png" alt />
+              </div>
+            </div>
           </div>
-          <div v-for="(sys, sysIndex) in imageList" :key="sysIndex" class="sysPic">
-            <img :src="sys.url" alt="" @click="chooseSysPic2(sys.url)" />
+          <div class="image-grid" v-loading="imageloading">
+            <div class="picNone" v-if="!imageList.length && !imageloading">
+              {{ lang.ssEnterKeywordSearch }}
+            </div>
+            <div v-for="(sys, sysIndex) in imageList" :key="sysIndex" class="image-item">
+              <img :src="sys.url" alt="" @click="chooseSysPic2(sys.url)" />
+            </div>
           </div>
         </div>
+        <div class="modal-footer web-search-footer">
+          <button class="btn-cancel" @click="sysPicVisible2 = false">{{ lang.ssCancel }}</button>
+          <button class="btn-confirm" @click="changePicture">{{ lang.ssChangeGrp }}</button>
+        </div>
       </div>
-    </el-dialog>
+    </div>
     <InteractiveToolDialog ref="InteractiveToolDialogRef" @addTool="addTool" />
     <VideoUploadDialog ref="VideoUploadDialogRef" @uploadLocalVideo="handleLocalVideoUpload"
       @uploadProgress="handleUploadProgress" @searchBilibili="handleBilibiliSearch" />
@@ -761,7 +770,7 @@
       <div class="modal-overlay"></div>
       <div class="modal-content publish-modal-content">
         <div class="modal-header">
-          <h3 class="modal-title">发布课程</h3>
+          <h3 class="modal-title">{{ lang.ssPublishCourse }}</h3>
           <div class="modal-close" @click="dialogVisiblePublish = false">×</div>
         </div>
         <div class="modal-body publish-modal-body">
@@ -777,8 +786,8 @@
             <div class="form-box-left">
               <div class="form-row">
                 <div class="form-item required" style="flex: 2;">
-                  <label class="form-label">学科</label>
-                  <el-select v-model="selectedSubject" placeholder="请选择学科" class="custom-select" collapse-tags multiple
+                  <label class="form-label">{{ lang.ssSubjectCategory }}</label>
+                  <el-select v-model="selectedSubject" :placeholder="lang.ssSelectSubj" class="custom-select" collapse-tags multiple
                     style="width: 100%">
                     <el-option v-for="option in subjectOptions" :key="option.id" :label="option.name"
                       :value="option.id"></el-option>
@@ -788,8 +797,8 @@
 
               <div class="form-row">
                 <div class="form-item required">
-                  <label class="form-label">年级</label>
-                  <el-select v-model="selectedGrade" placeholder="请选择年级" class="custom-select" collapse-tags multiple
+                  <label class="form-label">{{ lang.ssGradeType }}</label>
+                  <el-select v-model="selectedGrade" :placeholder="lang.ssSelectGrade" class="custom-select" collapse-tags multiple
                     style="width: 100%">
                     <el-option v-for="option in gradeOptions" :key="option.id" :label="option.name"
                       :value="option.id"></el-option>
@@ -797,8 +806,8 @@
                 </div>
 
                 <div class="form-item required">
-                  <label class="form-label">班级</label>
-                  <el-select v-model="checkboxList2" placeholder="请选择班级" class="custom-select" collapse-tags multiple
+                  <label class="form-label">{{ lang.ssClass }}</label>
+                  <el-select v-model="checkboxList2" :placeholder="lang.ssSelectClass" class="custom-select" collapse-tags multiple
                     style="width: 100%">
                     <el-option v-for="option in classOptions" :key="option.id" :label="option.name"
                       :value="option.id"></el-option>
@@ -808,15 +817,15 @@
 
               <div class="form-row">
                 <div class="form-item required">
-                  <label class="form-label">可见范围</label>
+                  <label class="form-label">{{ lang.ssVisibilityRange }}</label>
                   <div class="radio-group">
                     <div class="radio-item" :class="{ active: !isTeacherSee }" @click="isTeacherSee = false">
                       <div class="custom-radio">
                         <div class="radio-inner" :class="{ checked: !isTeacherSee }"></div>
                       </div>
                       <div>
-                        <label>仅发布学生可见</label>
-                        <div class="radio-desc">仅对发布的班级学生可见,其他人无法访问</div>
+                        <label>{{ lang.ssOnlyStudentsVisible }}</label>
+                        <div class="radio-desc">{{ lang.ssOnlyStudentsDesc }}</div>
                       </div>
                     </div>
                     <div class="radio-item" :class="{ active: isTeacherSee }" @click="isTeacherSee = true">
@@ -824,8 +833,8 @@
                         <div class="radio-inner" :class="{ checked: isTeacherSee }"></div>
                       </div>
                       <div>
-                        <label>组织可见</label>
-                        <div class="radio-desc">学校内所有教师均可查看</div>
+                        <label>{{ lang.ssOrganizationVisible }}</label>
+                        <div class="radio-desc">{{ lang.ssOrganizationDesc }}</div>
                       </div>
                     </div>
                   </div>
@@ -836,16 +845,16 @@
                 <button class="btn-publish" @click="confirmPublish">
                   <!-- <i class="el-icon-loading" v-if="publishing"></i>
                   <i class="el-icon-right" v-else></i> -->
-                  确认发布
+                  {{ lang.ssConfirmPublish }}
                 </button>
               </div>
             </div>
             <div class="form-box-right">
               <div class="form-row">
                 <div class="form-item" style="flex: 1;">
-                  <label class="form-label">课程封面</label>
-                  <div class="cover-upload-area" @click="choosePicVisible = true" v-if="!cover.length">
-                    <div class="cover-placeholder">
+                  <label class="form-label">{{ lang.ssCourseCover }}</label>
+                  <div class="cover-upload-area" v-loading="avatar_loading">
+                    <div class="cover-placeholder" v-if="!cover.length">
                       <svg width="48" height="48" viewBox="0 0 48 48" fill="none" xmlns="http://www.w3.org/2000/svg">
                         <g id="Component 2">
                           <path id="Vector"
@@ -857,15 +866,36 @@
                           <path id="Vector_3" d="M42 30L32 20L10 42" stroke="#D1D5DB" stroke-width="4" />
                         </g>
                       </svg>
-                      <span>点击选择上传方式</span>
+                      <span>{{ lang.ssHoverToSelectUpload }}</span>
                     </div>
-                  </div>
-                  <div v-else class="cover-preview" @click="choosePicVisible = true"
-                    :class="{ uploadFm2: cover.length }">
-                    <img :src="cover[0].url" alt="" class="cover_p" />
-                    <div class="cover_mask">
-                      <img src="../../../assets/icon/new/cover_update.png" /><span style="margin-top:5px;">{{
-                        lang.ssModifyCover }}</span>
+                    <div class="cover-preview" v-else>
+                      <img :src="cover[0].url" alt="" class="cover_p" />
+                    </div>
+                    <div class="upload-options">
+                      <div class="option-item" @click="uploadFromLocal($event)">
+                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+                          <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
+                          <polyline points="17 8 12 3 7 8"/>
+                          <line x1="12" y1="3" x2="12" y2="15"/>
+                        </svg>
+                        <span>{{ lang.ssUploadFromLocal }}</span>
+                        <input type="file" accept="image/*" style="display: none" @change="beforeUpload1" />
+                      </div>
+                      <div class="option-item" @click="searchFromWeb">
+                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+                          <circle cx="11" cy="11" r="8"/>
+                          <path d="M21 21l-4.35-4.35"/>
+                        </svg>
+                        <span>{{ lang.ssSearchFromWeb }}</span>
+                      </div>
+                      <div class="option-item" @click="generateFromAI">
+                        <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
+                          <path d="M12 2L2 7l10 5 10-5-10-5z"/>
+                          <path d="M2 17l10 5 10-5"/>
+                          <path d="M2 12l10 5 10-5"/>
+                        </svg>
+                        <span>{{ lang.ssGenerateFromAI }}</span>
+                      </div>
                     </div>
                   </div>
                 </div>
@@ -1113,6 +1143,7 @@ export default {
       selectedGrade: [],
       subjectOptions: [],
       gradeOptions: [],
+      avatar_loading: false,
     };
   },
   directives: {
@@ -1337,6 +1368,64 @@ export default {
       this.nextSteps();
       // this.$message.success('课程发布成功');
     },
+    // 自本地上传
+    uploadFromLocal(e) {
+      console.log('自本地上传');
+      var el = e.currentTarget;
+      el.getElementsByTagName("input")[0].click();
+      e.target.value = "";
+    },
+    // 自网页搜索
+    searchFromWeb() {
+      console.log('自网页搜索');
+      // 触发现有的网页搜索功能
+      this.searchImageValue = this.courseName;
+      this.resetImage();
+    },
+    // 自AI生成
+    generateFromAI() {
+      console.log('自AI生成');
+      if(!this.courseName){
+        this.$message.error(this.lang.ssFillCourseName);
+        return;
+      }
+      if(this.avatar_loading){
+        this.$message.error(this.lang.xiaokeloading);
+        return;
+      }
+      this.avatar_loading = true;
+			const prompt = [
+				{
+					role: "user",
+					content: `你是一名一流的UI设计师,现在你需要为这个场景设计相应的图标:
+									「${this.courseName}」 
+									请给出3个不同的设计方案。
+									## 要求 在你给出的设计方案中,坚持同时满足以下要求 
+									0. 要求体现学科特点 或者教学特点。
+									1. 背景底色为白色,正方形画面,画面填充满内容。 
+									2. 采用扁平化的设计风格 
+									3.不要在设计中设计到相关的文字,不如这个描述不允许类似这样的描述“繁体中文的“文”字,突出语言特色。”。 
+									## 输出一个json格式的回复,输出格式如下:
+									{"option":["方案一 核心视觉元素(2个):xxx 辅助视觉元素(1-3个):xxxx 核心情绪:xxx 主体颜色:xxx设计风格:xxxx 整体构图与画面内容:xxxx","方案二......","方案三......"]}`,
+				},
+			];
+			this.chat_no_stream(prompt, { type: "json_object" }).then((res) => {
+				console.log(res);
+				let aimap = JSON.parse(res).option;
+				this.ai_generate_image(aimap[0]).then(
+					(res) => {
+            this.cover = [
+                {
+                  name: res,
+                  url: res,
+                  uid: uuidv4()
+                }
+              ]
+						this.avatar_loading = false;
+					}
+				);
+			});
+    },
     closePan(tool) {
       if (tool == 15) {
         if (JSON.stringify(this.answerQ) == JSON.stringify(this.answerQ2)) {
@@ -2229,7 +2318,7 @@ export default {
       _this.ajax
         .post("https://gpt.cocorobo.cn/search_image", {
           page: _this.ppage,
-          pagesize: 9,
+          pagesize: 6,
           query: _this.searchImageValue
         })
         .then(function (response) {
@@ -3762,11 +3851,9 @@ export default {
 
 .picNone {
   position: absolute;
-  left: 50%;
-  top: 45%;
-  transform: translate(-50%, -50%);
-  width: fit-content;
+  width: 100%;
   color: #9c9c9c;
+  text-align: center;
 }
 
 .sysPic {
@@ -3804,6 +3891,10 @@ export default {
   margin-bottom: 10px;
 }
 
+.search_input >>> .el-input__inner{
+  padding: 0 30px 0 15px;
+}
+
 .search_img {
   width: 20px;
   height: 20px;
@@ -3811,6 +3902,7 @@ export default {
   right: 30px;
   top: 50%;
   transform: translateY(-50%);
+  cursor: pointer;
 }
 
 .search_img>img {
@@ -4884,6 +4976,7 @@ export default {
   cursor: pointer;
   transition: all 0.3s;
   background: #fafbfc;
+  position: relative;
 }
 
 .cover-preview {
@@ -4920,6 +5013,48 @@ export default {
   font-size: 12px;
 }
 
+.upload-options {
+  position: absolute;
+  top: 50%;
+  left: 50%;
+  transform: translate(-50%, -50%);
+  background-color: white;
+  border-radius: 8px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+  padding: 12px;
+  display: none;
+  z-index: 10;
+  min-width: 160px;
+}
+
+.cover-upload-area:hover .upload-options {
+  display: block;
+}
+
+.option-item {
+  display: flex;
+  align-items: center;
+  padding: 8px 12px;
+  border-radius: 4px;
+  cursor: pointer;
+  transition: all 0.3s;
+  margin-bottom: 4px;
+}
+
+.option-item:last-child {
+  margin-bottom: 0;
+}
+
+.option-item:hover {
+  background-color: #fff4e5;
+  color: #f78b22;
+}
+
+.option-item svg {
+  margin-right: 8px;
+  width: 16px;
+}
+
 .publish-modal-footer {
   padding: 16px 0 0;
   display: flex;
@@ -4949,4 +5084,110 @@ export default {
 .btn-publish i {
   margin-right: 8px;
 }
+
+/* 网页搜索图片弹窗 */
+.web-search-modal {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 1001;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.web-search-content {
+  width: 710px;
+  background-color: white;
+  border-radius: 8px;
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+  overflow: hidden;
+}
+
+.web-search-body {
+  padding: 24px;
+}
+
+.search-section {
+  margin-bottom: 20px;
+}
+
+.search-label {
+  display: block;
+  margin-bottom: 8px;
+  font-weight: 500;
+  color: #6B7280;
+}
+
+.image-grid {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 15px;
+  /* margin-bottom: 20px; */
+  position: relative;
+}
+
+.image-item {
+  width: 100%;
+  aspect-ratio: 16 / 9;
+  overflow: hidden;
+  border-radius: 10px;
+  cursor: pointer;
+  transition: all 0.3s;
+}
+
+.image-item:hover {
+  transform: scale(1.02);
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.image-item img {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+
+.web-search-footer {
+  padding: 16px 24px;
+  /* border-top: 1px solid #f0f0f0; */
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+}
+
+.web-search-footer .btn-cancel {
+  padding: 8px 16px;
+  border: 1px solid #dcdfe6;
+  border-radius: 10px;
+  background-color: white;
+  color: #606266;
+  cursor: pointer;
+  font-size: 14px;
+  transition: all 0.3s;
+  flex: 1;
+}
+
+.web-search-footer .btn-cancel:hover {
+  border-color: #c6e2ff;
+  color: #409eff;
+}
+
+.web-search-footer .btn-confirm {
+  background-color: #ff7d00;
+  border: 1px solid #ff7d00;
+  color: white;
+  padding: 10px 16px;
+  border-radius: 10px;
+  cursor: pointer;
+  font-size: 14px;
+  transition: all 0.3s;
+  font-weight: 500;
+  flex: 1;
+}
+
+.web-search-footer .btn-confirm:hover {
+  background-color: #e67300;
+}
 </style>

+ 14 - 0
src/lang/cn.json

@@ -1048,6 +1048,20 @@
   "ssQChainNotGen":"问题链还未生成完,请前往查看,回答完毕后再次操作。",
   "ssCourseSettings":"课程设置",
   "ssApply":"应用",
+  "ssPublishCourse":"发布课程",
+  "ssVisibilityRange":"可见范围",
+  "ssOnlyStudentsVisible":"仅发布学生可见",
+  "ssOnlyStudentsDesc":"仅对发布的班级学生可见,其他人无法访问",
+  "ssOrganizationVisible":"组织可见",
+  "ssOrganizationDesc":"学校内所有教师均可查看",
+  "ssCourseCover":"课程封面",
+  "ssHoverToSelectUpload":"悬浮选择上传方式",
+  "ssUploadFromLocal":"自本地上传",
+  "ssSearchFromWeb":"自网页搜索",
+  "ssGenerateFromAI":"自AI生成",
+  "ssWebSearchImage":"网页搜索图片",
+  "ssSearchKeyword":"搜索关键词",
+  "ssConfirmPublish":"确认发布",
   "ssTargetNotGen":"目标层还未生成完,请前往查看,回答完毕后再次操作。",
   "ssTaskClusNotGen":"任务簇还未生成完,请前往查看,回答完毕后再次操作。",
   "ssGenDescFirst":"请生成简要描述后再生成大纲",

+ 14 - 0
src/lang/en.json

@@ -1043,6 +1043,20 @@
   "ssQChainNotGen":"Question chain is not yet complete, please check and try again after answering.",
   "ssCourseSettings":"Course Settings",
   "ssApply":"Apply",
+  "ssPublishCourse":"Publish Course",
+  "ssVisibilityRange":"Visibility Range",
+  "ssOnlyStudentsVisible":"Only students visible",
+  "ssOnlyStudentsDesc":"Only visible to students in the published class, others cannot access",
+  "ssOrganizationVisible":"Organization visible",
+  "ssOrganizationDesc":"All teachers in the school can view",
+  "ssCourseCover":"Course Cover",
+  "ssHoverToSelectUpload":"Hover to select upload method",
+  "ssUploadFromLocal":"Upload from local",
+  "ssSearchFromWeb":"Search from web",
+  "ssGenerateFromAI":"Generate from AI",
+  "ssWebSearchImage":"Web Search Image",
+  "ssSearchKeyword":"Search Keyword",
+  "ssConfirmPublish":"Confirm Publish",
   "ssTargetNotGen":"Target layer is not yet complete, please check and try again after answering.",
   "ssTaskClusNotGen":"Task cluster is not yet complete, please check and try again after answering.",
   "ssGenDescFirst":"Please generate a brief description before generating the outline",

+ 14 - 0
src/lang/hk.json

@@ -1046,6 +1046,20 @@
   "ssQChainNotGen":"問題鏈還未生成完,請前往查看,回答完畢後再次操作。",
   "ssCourseSettings":"課程設定",
   "ssApply":"應用",
+  "ssPublishCourse":"發佈課程",
+  "ssVisibilityRange":"可見範圍",
+  "ssOnlyStudentsVisible":"僅發佈學生可見",
+  "ssOnlyStudentsDesc":"僅對發佈的班級學生可見,其他人無法訪問",
+  "ssOrganizationVisible":"組織可見",
+  "ssOrganizationDesc":"學校內所有教師均可查看",
+  "ssCourseCover":"課程封面",
+  "ssHoverToSelectUpload":"懸浮選擇上傳方式",
+  "ssUploadFromLocal":"自本地上傳",
+  "ssSearchFromWeb":"自網頁搜索",
+  "ssGenerateFromAI":"自AI生成",
+  "ssWebSearchImage":"網頁搜索圖片",
+  "ssSearchKeyword":"搜索關鍵詞",
+  "ssConfirmPublish":"確認發佈",
   "ssTargetNotGen":"目標層還未生成完,請前往查看,回答完畢後再次操作。",
   "ssTaskClusNotGen":"任務簇還未生成完,請前往查看,回答完畢後再次操作。",
   "ssGenDescFirst":"請生成簡要描述後再生成大綱",

+ 55 - 1
src/mixins/mixin.js

@@ -1,7 +1,10 @@
+import { v4 as uuidv4 } from "uuid";
+
 export const myMixin = {
   data() {
     return {
         userJson: {},
+        new_url: 'https://appapi.cocorobo.cn'
     };
   },
   methods: {
@@ -79,6 +82,57 @@ export const myMixin = {
           console.log("保存失败");
           console.log(e);
         });
-    }
+    },
+    async chat_no_stream(prompt=[], response_format = {
+      "type": "text"
+    }) {
+      return await new Promise((resolve) => {
+        let uid = uuidv4();
+        let data = JSON.stringify({
+          model: 'gpt-4o-2024-11-20',
+          temperature: 0,
+          max_tokens: 4096,
+          top_p: 1,
+          frequency_penalty: 0,
+          presence_penalty: 0,
+          messages: prompt,
+          uid: uid,
+          mind_map_question: '',
+          stream: false,
+          response_format: response_format
+        });
+        let config = {
+          method: 'post',
+          url: this.new_url + '/api/common/chat',
+          headers: {
+            'Content-Type': 'application/json'
+          },
+          data: data,
+        };
+        this.ajax.post(config.url,data)
+          .then((response) => {
+            resolve(response.data.FunctionResponse.choices[0].message.content);
+          })
+          .catch( (error)=> {
+            console.log(error);
+            //this.$message.error('服务器繁忙');
+            resolve(false);
+          });
+      });
+    },
+    async ai_generate_image(prompt) {
+      return await new Promise((resolve) => {
+        this.ajax
+          .post(this.new_url + "/api/agents/generateImage", {
+            prompt,
+            size: "1024x1024"
+          }).then(res => {
+            resolve(res.data);
+          }).catch(function (error) {
+            console.log(error);
+            resolve(false);
+          });
+      })
+    },
   }
 };