Browse Source

Merge branch 'master' of http://git.bozedu.net:3000/bzkf30/ai_mooc_h5

bzkf30 1 year ago
parent
commit
2c446c6418

+ 4 - 1
package.json

@@ -4,16 +4,19 @@
   "version": "0.0.0",
   "type": "module",
   "scripts": {
-    "dev": "vite",
+    "dev": "vite --host",
     "build": "vue-tsc && vite build",
     "preview": "vite preview"
   },
   "dependencies": {
+    "@vitejs/plugin-basic-ssl": "^1.0.2",
     "@vueuse/core": "^10.1.2",
     "axios": "^1.6.2",
+    "recorder-js": "^1.0.7",
     "sass": "1.62.1",
     "unocss": "^0.58.0",
     "vant": "^4.8.0",
+    "vconsole": "^3.15.1",
     "vite-plugin-pages": "^0.32.0",
     "vite-plugin-windicss": "^1.9.2",
     "vue": "^3.3.11",

File diff suppressed because it is too large
+ 1974 - 1895
pnpm-lock.yaml


BIN
src/assets/caiji/framerx.webp


+ 8 - 0
src/main.ts

@@ -5,6 +5,14 @@ import App from './App.vue'
 import './style.css'
 import 'virtual:uno.css'
 
+import 'vant/es/toast/style';
+import 'vant/es/dialog/style';
+import 'vant/es/notify/style';
+import 'vant/es/image-preview/style';
+
+import VConsole from 'vconsole'
+new VConsole()
+
 const app = createApp(App)
 
 app.use(router)

+ 0 - 34
src/pages/caiji/index.vue

@@ -1,34 +0,0 @@
-<script setup lang='ts'>
-import rx from '~/assets/caiji/rx.webp'
-import sy from '~/assets/caiji/sy.webp'
-
-const router = useRouter()
-
-</script>
-
-<template>
-  <van-nav-bar title="AI慕课采集"></van-nav-bar>
-  <div class="flex flex-col items-center space-y-6 px-24px">
-    <div class="relative" @click="router.push({ name: 'home-caiji-sy-read' })">
-      <img :src="sy" alt="">
-      <div class="absolute inset-0 flex flex-col items-center text-white font-bold mt-40px space-y-20px">
-        <div>声音采集</div>
-        <van-button plain round size="small" type="success" class="px-4">点击进入</van-button>
-      </div>
-    </div>
-    <div class="relative">
-      <img :src="rx" alt="">
-      <div class="absolute inset-0 flex flex-col items-center text-white font-bold mt-40px space-y-20px">
-        <div>人像采集</div>
-        <van-button plain round size="small" type="success" class="px-4">点击进入</van-button>
-      </div>
-    </div>
-    <ul class="w-full text-xs text-white leading-5 opacity-60 space-y-4 list-disc list-inside -indent-4 pl-4">
-      <li>录音时位置确定后尽量不要动,就是说嘴和话筒的距离确定后不要随便乱动,有的人好动,这个避免不了,但是幅度不要太大,要不然会影响音色和动态。</li>
-      <li>拍摄时现场严禁大声喧哗,嬉笑打闹;人脸正面免冠照片,需露出眉毛、眼睛及耳朵,亮度均衡。</li>
-    </ul>
-
-  </div>
-</template>
-
-

+ 7 - 0
src/pages/caiji/rx/name.vue

@@ -0,0 +1,7 @@
+<script setup lang='ts'>
+
+</script>
+
+<template>
+  <div></div>
+</template>

+ 39 - 0
src/pages/caiji/rx/read.vue

@@ -0,0 +1,39 @@
+<script setup lang='ts'>
+import frame from '~/assets/caiji/framerx.webp'
+
+const router = useRouter()
+
+function onClickLeft() {
+  router.back()
+}
+
+const leftTime = ref(15)
+const timer = ref()
+
+onMounted(() => {
+  timer.value = setInterval(() => {
+    leftTime.value--
+    if (leftTime.value === 0) {
+      clearInterval(timer.value)
+    }
+  }, 1000)
+})
+
+
+</script>
+
+<template>
+  <van-nav-bar title="人像采集" left-arrow @click-left="onClickLeft"></van-nav-bar>
+  <div class="flex-auto w-full flex flex-col items-center justify-between py-72px px-6 box-border">
+    <div class="w-full flex flex-col items-center mb-12">
+      <img class="w-108px mt-6px" :src="frame" alt="">
+      <div class="text-xl mt-16px">定制你的专属人像</div>
+      <div class="text-base mt-22px">保持环境安静</div>
+      <div class="text-base">按照指令做出动作</div>
+    </div>
+
+    <van-button v-if="leftTime" type="danger" size="large" block disabled>{{leftTime}}s 我已知悉</van-button>
+    <van-button v-else type="success" size="large" block icon="success" @click="router.replace({name:'caiji-rx-name'})" >我已知悉</van-button>
+
+  </div>
+</template>

+ 7 - 0
src/pages/caiji/rx/record/[id].vue

@@ -0,0 +1,7 @@
+<script setup lang='ts'>
+
+</script>
+
+<template>
+  <div></div>
+</template>

+ 50 - 3
src/pages/caiji/sy/name.vue

@@ -1,4 +1,5 @@
 <script setup lang='ts'>
+import request from '~/request'
 const router = useRouter()
 
 function onClickLeft() {
@@ -6,13 +7,59 @@ function onClickLeft() {
 }
 
 const form = reactive({
-  axa_sycj_zdysyid_v2: undefined,
+  // axa_sycj_zdysyid_v2: undefined,
   axa_name: undefined,
   axa_mssc: '',
   axa_sycj_syxb_v2: 'male'
 })
 function onSubmit(values: any) {
   console.log('submit', values);
+  request({
+    url: '/aimooc/xnszr_audio/add',
+    data: {
+      aimooc_xnszr_audio: {
+        ...form,
+        axa_is_system: 2
+      }
+    }
+  }).then(res => {
+    const info = res.data.one_info
+    // {
+    //   "axa_id": "58",
+    //   "axa_sycj_json_v2": "",
+    //   "axa_sycj_syxb_v2": "male",
+    //   "axa_sycj_sywj_v2": "bbe539bb8ad24e4ea14e36e16922c9cf",
+    //   "axa_sycj_zdysyid_v2": "szbzaimooc170348221150",
+    //   "axa_sycj_shzt_v2": "3",
+    //   "axa_sycj_scbz_v2": "0",
+    //   "axa_keyword": "",
+    //   "axa_name": "朱凡1",
+    //   "axa_img": "",
+    //   "axa_file": "",
+    //   "axa_code": "",
+    //   "axa_yyfw": "",
+    //   "axa_bzms": "",
+    //   "axa_mssc": "朱凡测试1",
+    //   "axa_status": "2",
+    //   "axa_ext": null,
+    //   "axa_scenario_v2": "",
+    //   "axa_is_system": "2",
+    //   "area_id1": "10",
+    //   "area_id2": "166",
+    //   "area_id3": "2069",
+    //   "area_id4": "0",
+    //   "sm_id": "3",
+    //   "dept_id": "0",
+    //   "user_id": "0",
+    //   "create_user_id": "199200",
+    //   "modify_user_id": "0",
+    //   "create_dateline": "1703482211",
+    //   "modify_dateline": "0",
+    //   "isdelete": "0",
+    //   "create_dateline_format": "2023-12-25 13:30:11"
+    // }
+    router.replace({ name: "caiji-sy-record-id", params: { id: info.axa_sycj_zdysyid_v2 } })
+  })
 };
 
 </script>
@@ -22,8 +69,8 @@ function onSubmit(values: any) {
   <div class="flex-auto py-4 ">
     <van-form class="h-full flex flex-col" @submit="onSubmit">
       <van-cell-group inset>
-        <van-field v-model="form.axa_sycj_zdysyid_v2" name="声音id" label="声音id" placeholder="自定义的声音id,英文或拼音"
-          :rules="[{ required: true, message: '' }]" />
+        <!-- <van-field v-model="form.axa_sycj_zdysyid_v2" name="声音id" label="声音id" placeholder="自定义的声音id,英文或拼音"
+          :rules="[{ required: true, message: '' }]" /> -->
         <van-field v-model="form.axa_name" name="声音名字" label="声音名字" placeholder="声音名字,一般为中文名"
           :rules="[{ required: true, message: '' }]" />
         <van-field name="性别" label="性别" :rules="[{ required: true, message: '' }]">

+ 1 - 1
src/pages/caiji/sy/read.vue

@@ -25,7 +25,7 @@ onMounted(() => {
 <template>
   <van-nav-bar title="声音采集" left-arrow @click-left="onClickLeft"></van-nav-bar>
   <div class="flex-auto w-full flex flex-col items-center justify-between py-72px px-6 box-border">
-    <div class="w-full flex flex-col items-center mb-12" @click="router.replace({name:'caiji-sy-name'})">
+    <div class="w-full flex flex-col items-center mb-12">
       <img class="w-108px mt-6px" :src="frame" alt="">
       <div class="text-xl mt-16px">定制你的专属声音</div>
       <div class="text-base mt-22px">保持环境安静</div>

+ 0 - 76
src/pages/caiji/sy/record.vue

@@ -1,76 +0,0 @@
-<script setup lang='ts'>
-
-const router = useRouter()
-
-function onClickLeft() {
-  router.back()
-}
-
-function startAudioRecord() {
-  // 检查浏览器是否支持 getUserMedia API
-if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
-  // 请求用户的音频输入
-  navigator.mediaDevices.getUserMedia({ audio: true })
-    .then(function(stream) {
-      // 创建一个 MediaRecorder 实例
-      const mediaRecorder = new MediaRecorder(stream);
-
-      // 创建一个数组来存储音频数据
-      const audioChunks = [];
-
-      // 当有音频数据可用时,将其添加到数组中
-      mediaRecorder.addEventListener("dataavailable", function(event) {
-        audioChunks.push(event.data);
-      });
-
-      // 当录音结束时,将音频数据转换为 Blob 对象
-      mediaRecorder.addEventListener("stop", function() {
-        const audioBlob = new Blob(audioChunks);
-        const audioUrl = URL.createObjectURL(audioBlob);
-        const audio = new Audio(audioUrl);
-        audio.play();
-      });
-
-      // 开始录音
-      mediaRecorder.start();
-
-      // 5秒后停止录音
-      setTimeout(function() {
-        mediaRecorder.stop();
-      }, 5000);
-    })
-    .catch(function(err) {
-      console.log(err);
-    });
-} else {
-  console.log("浏览器不支持相关 API");
-}
-}
-
-
-</script>
-
-<template>
-  <van-nav-bar title="声音采集" left-arrow @click-left="onClickLeft"></van-nav-bar>
-  <div class="flex-auto w-full flex flex-col items-center justify-between py-72px px-6 box-border">
-    <div class="w-full flex flex-col items-center mb-12">
-      <div class="text-xl mt-26px">1 / 15</div>
-      <p class="text-base indent mt-28px">宁愿用aac也不要用mp3,aac从音质上来说是比mp3要有保证,对声纹模型识别的效果也是有效的,即使aac的压缩比和mp3差不多大。不过,条件允许的话,还是用wav最好了,保16KHz16bit音频的wav,对声纹模型识别是最佳。</p>
-    </div>
-
-
-    <div class="flex w-full justify-around text-hex-242731">
-      <div class="p-4 rounded-8 bg-white ">
-        <van-icon name="volume" size="28" />
-      </div>
-      <div class="p-4 rounded-8 bg-white ">
-        <van-icon name="play" size="28" />
-        <van-icon name="pause" size="28" />
-        <van-icon name="stop" size="28" />
-      </div>
-      <div class="p-4 rounded-8 bg-white ">
-        <van-icon name="success" size="28" />
-      </div>
-    </div>
-  </div>
-</template>

+ 216 - 0
src/pages/caiji/sy/record/[id].vue

@@ -0,0 +1,216 @@
+<script setup lang='ts'>
+import request from '~/request'
+import { showFailToast, showSuccessToast, showDialog } from 'vant';
+
+
+const props = defineProps<{
+  id: string
+}>()
+console.log('props : ', props.id)
+
+const router = useRouter()
+
+function onClickLeft() {
+  router.back()
+}
+
+const loading = ref(true)
+
+const current = ref(0)
+
+// {
+//   audioId: '1',
+//   demoAudio:
+//     'http://nls-cloud-cn-shanghai.oss-cn-shanghai.aliyuncs.com/portal/tts/text_audio/wave/tonghua/300001.wav?Expires=1703561504&OSSAccessKeyId=LTAIiIg37IN8xeMa&Signature=guvV7UOLaCby%2FYKjuIfQAzGa4DY%3D&response-content-disposition=attachment%3Bfilename%3Dportal%2Ftts%2Ftext_audio%2Fwave%2Ftonghua%2F300001.wav',
+//   text: '希望我们大家都能像他一样。'
+// }
+const list = ref<any[]>([])
+
+request({
+  url: '/aimooc/xnszr_audio/get_demo_text'
+}).then(res => {
+
+  list.value = Object.values(res.data).sort((a: any, b: any) => a.audioId - b.audioId)
+  console.log('list.value : ', list.value)
+  loading.value = false
+})
+
+function playAudio() {
+  const audio = new Audio(list.value[current.value].demoAudio);
+  audio.play().then(() => {
+    console.log('audio play success')
+  }).catch((error) => {
+    console.error('Failed to play audio: ' + error);
+    showFailToast('播放失败');
+  });
+}
+
+
+
+let mediaRecorder: MediaRecorder | null = null
+let isRecording = ref(false)
+
+let audioChunks: any[] = []
+
+function startAudioRecord() {
+  console.log('点击 开始录音')
+  if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
+    console.log('浏览器支持相关 API')
+    navigator.mediaDevices.getUserMedia({ audio: true })
+      .then((stream) => {
+        console.log('stream')
+        isRecording.value = true
+
+        mediaRecorder = new MediaRecorder(stream);
+        console.log('mediaRecorder 初始化成功 ')
+
+        mediaRecorder.addEventListener("dataavailable", (event: any) => {
+          audioChunks.push(event.data);
+        });
+
+        mediaRecorder.addEventListener("stop", function (event: any) {
+          audioChunks.push(event.data);
+        });
+
+        console.log("mediaRecorder 开始录音")
+        mediaRecorder.start(500);
+      })
+      .catch((err) => {
+        console.error(err);
+      });
+  } else {
+    console.log("浏览器不支持相关 API", navigator.mediaDevices);
+    showFailToast('你的浏览器版本过低,不支持录音功能');
+    // showFailToast('请先去设置中打开浏览器录音权限');
+  }
+}
+
+function stopAudioRecord() {
+  if (!mediaRecorder) {
+    console.log('停止录音 录音机未启动时触发')
+    return
+  }
+  console.log('停止录音')
+  mediaRecorder.stop();
+  mediaRecorder = null
+  isRecording.value = false
+}
+
+onUnmounted(() => {
+  if (mediaRecorder) {
+    mediaRecorder.stop();
+    mediaRecorder = null
+  }
+})
+
+function submitAudio() {
+  return new Promise((resolve, reject) => {
+
+    stopAudioRecord()
+    console.log('audioChunks : ', audioChunks.length)
+    if (audioChunks.length === 0) {
+      showFailToast('请先录音');
+      return reject()
+    }
+    setTimeout(() => {
+      const audioBlob = new Blob(audioChunks, { type: 'audio/wav' });
+      console.log('audioBlob : ', audioBlob)
+      // const filedata = new File([audioBlob], 'audio.wav', { type: 'audio/wav' });
+      mediaRecorder = null
+
+      request({
+        url: '/upload/main/file',
+        data: {
+          filedata: audioBlob,
+          aliyun_oss: 1
+        },
+        transformRequest: [
+          function (data: any) {
+            const formData = new FormData();
+            Object.keys(data).forEach(key => formData.append(key, data[key]))
+            return formData;
+          },
+        ],
+      })
+        // Promise.resolve({})
+        .then((res) => {
+          if (res.code !== '1') {
+            return reject()
+          }
+          // return res
+          const url = res.data.oss_url
+          // console.log('https://aimoocapi.bozedu.net/' + url)
+          console.log(url)
+          return request({
+            url: '/aimooc/xnszr_audio/detect_audio',
+            data: {
+              voiceId: props.id,
+              audioRecordId: list.value[current.value].audioId,
+              recordUrl: url,
+            }
+          })
+        }).then((res: any) => {
+          if (current.value === list.value.length) {
+            request({
+              url: '/aimooc/xnszr_audio/submit_job',
+              data: {
+                voiceId: props.id,
+              }
+            })
+            showDialog({
+              title: '成功提醒',
+              message: '采集成功',
+              showCancelButton: false,
+              confirmButtonText: '确定',
+            }).then(() => {
+              router.back()
+            })
+          } else {
+            if (res?.code === '1') {
+              current.value++
+              showSuccessToast('声音采集识别成功')
+            } else {
+              showFailToast(res?.msg)
+            }
+          }
+          resolve(res?.data)
+        }).catch(err => {
+          reject(err)
+        })
+    }, 500);
+  })
+
+}
+
+</script>
+
+<template>
+  <van-nav-bar title="声音采集" left-arrow @click-left="onClickLeft"></van-nav-bar>
+  <div class="flex-auto w-full flex flex-col items-center justify-between py-72px px-6 box-border">
+    <van-loading v-if="loading" size="36px" vertical class="text-xl">加载中...</van-loading>
+    <template v-else>
+      <div class="w-full flex flex-col items-center mb-12">
+        <div class="text-xl mt-26px"><span class="text-hex-DB664D">{{ current + 1 }}</span> / {{ list.length }}</div>
+        <p class="text-base indent mt-28px">
+          {{ list[current]?.text }}
+        </p>
+      </div>
+
+
+      <div class="flex w-full justify-around text-hex-242731">
+        <div class="p-4 rounded-8 bg-white " @click="playAudio">
+          <van-icon name="volume" size="28" />
+        </div>
+        <div class="p-4 rounded-8 bg-white " @click="() => isRecording ? stopAudioRecord() : startAudioRecord()">
+          <van-icon v-show="!isRecording" name="play" size="28" />
+          <!-- <van-icon name="pause" size="28" /> -->
+          <van-icon v-show="isRecording" name="stop" size="28" />
+        </div>
+        <div class="p-4 rounded-8 bg-white " @click="submitAudio">
+          <van-icon name="success" size="28" />
+        </div>
+      </div>
+    </template>
+
+  </div>
+</template>

+ 1 - 1
src/pages/home/caiji/index.vue

@@ -16,7 +16,7 @@ const router = useRouter()
         <van-button plain round size="small" type="success" class="px-4">点击进入</van-button>
       </div>
     </div>
-    <div class="relative">
+    <div class="relative" @click="router.push({ name: 'caiji-rx-read' })">
       <img :src="rx" alt="">
       <div class="absolute inset-0 flex flex-col items-center text-white font-bold mt-40px space-y-20px">
         <div>人像采集</div>

+ 59 - 0
src/request.ts

@@ -0,0 +1,59 @@
+import axios from 'axios'
+
+// const user = JSON.parse(localStorage.getItem('userInfo') ?? 'null');
+// const token = user?.token
+const token = '0ccdZ9QR_a7mNT9eNB784IbdZC3cJBtRMbuXWg_btjWlPsYX_bLlRN6RJ2Rtq3vsHs90gRM51pbxYqamXsT18QuDMQn0cQ'
+
+const isDev = false
+
+const baseURL = isDev ? 'https://aimoocapi.bozedu.net' : 'https://aimoocapi.bozedu.net'
+
+const Request = axios.create({
+  baseURL,
+  method: 'post',
+  timeout: 60000,
+  headers: {
+    'Content-Type': 'application/x-www-form-urlencoded',
+  },
+})
+
+Request.interceptors.request.use(
+  (config) => {
+    config.data = Object.assign(
+      {
+        token,
+        api: 'json',
+        issubmit: 1
+      },
+      config.data || {}
+    )
+    if (config.method === 'get') {
+      config.params = Object.assign(
+        {
+          token,
+          api: 'json',
+          fake: 1,
+        },
+        config.params || {}
+      )
+    }
+    return config
+  },
+  (error) => {
+    console.error('request error: ', error)
+    return Promise.reject(error)
+  }
+)
+
+// response interceptor
+Request.interceptors.response.use(
+  (response) => {
+    return response.data
+  },
+  (error) => {
+    console.error('response error: ' + error)
+    return Promise.reject(error)
+  }
+)
+
+export default Request

+ 5 - 1
vite.config.ts

@@ -10,6 +10,8 @@ import UnoCSS from 'unocss/vite'
 import VueMacros from 'unplugin-vue-macros/vite'
 import { VantResolver } from 'unplugin-vue-components/resolvers'
 import legacyPlugin from '@vitejs/plugin-legacy'
+
+import basicSsl from '@vitejs/plugin-basic-ssl'
 export default defineConfig({
   base: '',
   resolve: {
@@ -19,9 +21,11 @@ export default defineConfig({
   },
   server: {
     hmr: { overlay: false }, // 禁用或配置 HMR 连接 设置 server.hmr.overlay 为 false 可以禁用服务器错误遮罩层
-    port: 9901
+    port: 9901,
+    https: true,
   },
   plugins: [
+    basicSsl(),
     legacyPlugin({
       targets: ['chrome 52'],  // 需要兼容的目标列表,可以设置多个
       additionalLegacyPolyfills: ['regenerator-runtime/runtime'] // 面向IE11时需要此插件

File diff suppressed because it is too large
+ 90 - 0
vite.config.ts.timestamp-1703645858199-369ba09737292.mjs