{this.state.text.split(' ').map((word) => word && '

本文深入探讨了React Native中使用&&操作符进行条件渲染的原理,解析了一段具体代码实现,展示了如何根据文本内容动态展示问号,以及&&在React Native中的独特用法,类似于Java中的三元运算符。

看了一遍官网,没理解什么意思,遂百度:终于知道&&和oc 里的&&不是一回事啊!

import React, { Component } from 'react';
import { AppRegistry, Text, TextInput, View } from 'react-native';

class PizzaTranslator extends Component { 
  constructor(props) { 
    super(props); 
    this.state = {text:''}; 
  } 
  render() { 
    return ( 
      <View style={{padding: 10}}> 
        <TextInput 
          style={{height: 40}} 
          placeholder="Type here to translate!" 
          onChangeText={(text) => this.setState({text})} 
        /> 
        <Text style={{padding: 10, fontSize: 42}}> 
          {this.state.text.split(' ').map((word) => word && '?').join(' ')} 
        </Text> 
      </View> 
    ); 
  }
}
// 注册应用(registerComponent)后才能正确渲染
// 注意:只把应用作为一个整体注册一次,而不是每个组件/模块都注册AppRegistry.registerComponent('PizzaTranslator', () => PizzaTranslator);

关于{this.state.text.split(' ').map((word) => word && '?').join(' ')}这段代码,具体可以理解为,text文本先根据空格分隔成数组,再通过map方法遍历,其中map((word)=>部分,word是遍历数组的item,=>代表匿名函数,&&则表示符号前的值不为空时,返回&&后的值,可以类似理解为java中的三元运算符相似的概念

<!-- 供应商质量管理-供应商索赔管理-修改和新增界面 --> <template> <div class="app-container"> <el-form ref="modelForm" :model="modalForm" :title="title" :rules="rules"> <div class="form-title"> {{ title }} </div> <div class="form-bordered"> <el-collapse v-model="activeNames"> <!-- 基本信息 --> <el-collapse-item name="1"> <template slot="title">{{ $t('BasicInformation') }}<span style="color: #004097; font-weight: 900">({{ $t('ClickToExpandOrcollapse') }})</span></template> <div class="form-bordered"> <!-- pd发起的流程 --> <template v-if="modalForm.roles == 'PD'"> <el-row> <!-- 编号 --> <el-col :span="12"> <el-form-item :label="$t('rm.rm.formCode')"> <span v-if="modalForm.formCode">{{ modalForm.formCode }}</span><span v-else style="color: red;">提交后自动生成</span> </el-form-item> </el-col> <!-- 来源 --> <el-col :span="12"> <el-form-item :label="$t('spl.supplierClaim.source')"> <el-input v-model="modalForm.source" clearable :placeholder="$t('pleaseEnter', { param: $t('spl.supplierClaim.source') })" /> </el-form-item> </el-col> </el-row> <el-row> <!-- 供方简称 --> <el-col :span="12"> <el-form-item :label="$t('spl.supplierClaim.shortName')" prop="shortName" :rules="[{ required: true, message: $t('pleaseEnter', { param: $t('spl.supplierClaim.shortName'), }), trigger: 'blur', },]"> <dataSelect v-model="modalForm.shortName" :title="'选择供应商主数据'" :api="getSupplierList" :columns="[ { label: $t('baseData.supplier.supplierCode'), code: 'supplierCode' }, { label: $t('baseData.supplier.supplierName'), code: 'supplierName' }, ]" :search-params-list="[ { label: $t('baseData.supplier.supplierCode'), code: 'supplierCode' }, { label: $t('baseData.supplier.supplierName'), code: 'supplierName' }, ]" :show-search="true" :on-page="false" :model-form="modalForm" :select-name="'shortName'" :single-select="true" :bind-object="{ label: 'supplierName', key: 'supplierName' }" :default-ids="modalForm.shortName" :default-names="modalForm.shortName" @selectUserClosepopIndex="(list) => doGetMainPd(list, modalForm)" /> </el-form-item> </el-col> <!-- 申请部门名称 --> <el-col :span="12"> <el-form-item :label="$t('spl.supplierClaim.applyDeptBy')" prop="applyDeptBy"> <org-picker ref="orgPickerApplyDeptBy" type="dept" :multiple="false" :parent-this="this" :form-props="'applyDeptBy'" :selected.sync="modalForm.applyDeptBy" /> </el-form-item> </el-col> </el-row> <el-row> <!-- 申请日期 --> <el-col :span="12"> <el-form-item :label="$t('spl.supplierClaim.applicatTime')" prop="applicatTime"> <el-date-picker v-model="modalForm.applicatTime" show-time clearable value-format="yyyy-MM-dd" /> </el-form-item> </el-col> <!-- 物料名称 --> <el-col :span="12"> <el-form-item :label="$t('spl.supplierClaim.materialName')" prop="materialCode"> <dataSelect v-model="modalForm.materialName" :title="'选择物料主数据'" :api="getMaterialMasterDataList" :columns="[{ label: '物料编码', code: 'materialCode' }, { label: '物料名称', code: 'materialName' }]" :search-params-list="[{ label: '物料编码', code: 'materialCode' }, { label: '物料名称', code: 'materialName' }]" :show-search="true" :on-page="false" :model-form="modalForm" :select-name="'materialName'" :single-select="false" :bind-object="{ label: 'materialName', key: 'materialCode' }" :default-ids="modalForm.materialCode" :default-names="modalForm.materialName" @selectUserClosepop="(list) => doGetMaterial(list, modalForm)" /> </el-form-item> </el-col> </el-row> <el-row> <!-- 物料编码 --> <el-col :span="12"> <el-form-item :label="$t('spl.supplierClaim.materialCode')" prop="materialCode"> <el-input disabled v-model="modalForm.materialCode" readonly /> </el-form-item> </el-col> <!-- 订单号 --> <el-col :span="12"> <el-form-item :label="$t('spl.supplierClaim.orderCode')"> <el-input v-model="modalForm.orderCode" clearable :placeholder="$t('pleaseEnter', { param: $t('spl.supplierClaim.orderCode') })" /> </el-form-item> </el-col> </el-row> <el-row> <!-- 数量 --> <el-col :span="12"> <el-form-item :label="$t('spl.supplierClaim.quantityNum')" prop="quantityNum"> <el-input v-model="modalForm.quantityNum" clearable :placeholder="$t('pleaseEnter', { param: $t('spl.supplierClaim.quantityNum') })" /> </el-form-item> </el-col> <!-- 质量协议 --> <el-col :span="12"> <el-form-item :label="$t('spl.supplierClaim.dealUrl')"> <fileUpload v-model="modalForm.dealUrl" :limit="1" :is-read-only="false" /> </el-form-item> </el-col> </el-row> <el-row> <!-- 异常描述 --> <el-col :span="12"> <el-form-item :label="$t('spl.supplierClaim.description')" prop="description"> <el-input v-model="modalForm.description" type="textarea" clearable @change="descriptionChange()" :placeholder="$t('pleaseEnter', { param: $t('spl.supplierClaim.description') })" /> </el-form-item> </el-col> <!-- 异常描述附件 --> <el-col :span="12"> <el-form-item :label="$t('spl.supplierClaim.descriptionUrl')"> <fileUpload v-model="modalForm.descriptionUrl" :limit="1" :is-read-only="false" /> </el-form-item> </el-col> </el-row> <el-row> <!-- sqe --> <el-col :span="12"> <el-form-item :label="$t('SQE')" prop="sqe"> <el-select v-model="modalForm.sqeName" :placeholder="$t('pleaseEnter', { param: $t('SQE') })" @change="sqeChange"> <el-option v-for="(item, index) in sqeList" :key="index" :label="item.label" :value="item.label"></el-option> </el-select> </el-form-item> </el-col> <el-col :span="12"></el-col> </el-row> </template> <template v-else> <el-row> <!-- 编号 --> <el-col :span="12"> <el-form-item :label="$t('rm.rm.formCode')"> <span v-if="modalForm.formCode">{{ modalForm.formCode }}</span><span v-else style="color: red;">提交后自动生成</span> </el-form-item> </el-col> <!-- 来源 --> <el-col :span="12"> <el-form-item :label="$t('spl.supplierClaim.source')" prop="source"> <el-input v-model="modalForm.source" clearable :placeholder="$t('pleaseEnter', { param: $t('spl.supplierClaim.source') })" /> </el-form-item> </el-col> </el-row> <el-row> <!-- 供方简称 --> <el-col :span="12"> <el-form-item :label="$t('spl.supplierClaim.shortName')" prop="shortName" :rules="[{ required: true, message: $t('pleaseEnter', { param: $t('spl.supplierClaim.shortName'), }), trigger: 'blur', },]"> <dataSelect v-model="modalForm.shortName" :title="'选择供应商主数据'" :api="getSupplierList" :columns="[ { label: $t('baseData.supplier.supplierCode'), code: 'supplierCode' }, { label: $t('baseData.supplier.supplierName'), code: 'supplierName' }, ]" :search-params-list="[ { label: $t('baseData.supplier.supplierCode'), code: 'supplierCode' }, { label: $t('baseData.supplier.supplierName'), code: 'supplierName' }, ]" :show-search="true" :on-page="false" :model-form="modalForm" :select-name="'shortName'" :single-select="true" :bind-object="{ label: 'supplierName', key: 'supplierName' }" :default-ids="modalForm.shortName" :default-names="modalForm.shortName" @selectUserClosepopIndex="(list) => doGetMain(list, modalForm)" /> </el-form-item> </el-col> <!-- 申请部门名称 --> <el-col :span="12"> <el-form-item :label="$t('spl.supplierClaim.applyDeptBy')" prop="applyDeptBy"> <org-picker ref="orgPickerApplyDeptBy" type="dept" :multiple="false" :parent-this="this" :form-props="'applyDeptBy'" :selected.sync="modalForm.applyDeptBy" /> </el-form-item> </el-col> </el-row> <el-row> <!-- 申请日期 --> <el-col :span="12"> <el-form-item :label="$t('spl.supplierClaim.applicatTime')" prop="applicatTime"> <el-date-picker v-model="modalForm.applicatTime" show-time clearable value-format="yyyy-MM-dd" /> </el-form-item> </el-col> <!-- 物料名称 --> <el-col :span="12"> <el-form-item :label="$t('spl.supplierClaim.materialName')" prop="materialCode"> <dataSelect v-model="modalForm.materialName" :title="'选择物料主数据'" :api="getMaterialMasterDataList" :columns="[{ label: '物料编码', code: 'materialCode' }, { label: '物料名称', code: 'materialName' }]" :search-params-list="[{ label: '物料编码', code: 'materialCode' }, { label: '物料名称', code: 'materialName' }]" :show-search="true" :on-page="false" :model-form="modalForm" :select-name="'materialName'" :single-select="false" :bind-object="{ label: 'materialName', key: 'materialCode' }" :default-ids="modalForm.materialCode" :default-names="modalForm.materialName" @selectUserClosepop="(list) => doGetMaterial(list, modalForm)" /> </el-form-item> </el-col> </el-row> <el-row> <!-- 物料编码 --> <el-col :span="12"> <el-form-item :label="$t('spl.supplierClaim.materialCode')" prop="materialCode"> <el-input disabled v-model="modalForm.materialCode" readonly /> </el-form-item> </el-col> <!-- 订单号 --> <el-col :span="12"> <el-form-item :label="$t('spl.supplierClaim.orderCode')"> <el-input v-model="modalForm.orderCode" clearable :placeholder="$t('pleaseEnter', { param: $t('spl.supplierClaim.orderCode') })" /> </el-form-item> </el-col> </el-row> <el-row> <!-- 数量 --> <el-col :span="12"> <el-form-item :label="$t('spl.supplierClaim.quantityNum')" prop="quantityNum"> <el-input v-model="modalForm.quantityNum" clearable :placeholder="$t('pleaseEnter', { param: $t('spl.supplierClaim.quantityNum') })" /> </el-form-item> </el-col> <!-- 质量协议 --> <el-col :span="12"> <el-form-item :label="$t('spl.supplierClaim.dealUrl')"> <fileUpload v-model="modalForm.dealUrl" :limit="1" :is-read-only="false" /> </el-form-item> </el-col> </el-row> <el-row> <!-- 异常描述 --> <el-col :span="12"> <el-form-item :label="$t('spl.supplierClaim.description')" prop="description"> <el-input v-model="modalForm.description" type="textarea" clearable @change="descriptionChange()" :placeholder="$t('pleaseEnter', { param: $t('spl.supplierClaim.description') })" /> </el-form-item> </el-col> <!-- 异常描述附件 --> <el-col :span="12"> <el-form-item :label="$t('spl.supplierClaim.descriptionUrl')"> <fileUpload v-model="modalForm.descriptionUrl" :limit="1" :is-read-only="false" /> </el-form-item> </el-col> </el-row> <el-row> <!-- sqe --> <el-col :span="12"> <el-form-item :label="$t('SQE')" prop="sqe"> <org-picker v-if="modalForm.roles == 'CQE'" ref="orgPickersqe" type="user" :multiple="false" :parent-this="this" :form-props="'sqe'" :selected.sync="modalForm.sqe" /> <org-picker-view v-else :value.sync="modalForm.sqe" /> </el-form-item> </el-col> <el-col :span="12"></el-col> </el-row> <el-row v-if="roleId == 1"> </el-row> </template> </div> </el-collapse-item> <el-collapse-item name="2"> <template slot="title">异常费用核算<span style="color: #004097; font-weight: 900">({{ $t('ClickToExpandOrcollapse') }})</span></template> <el-tabs v-model="activeTab" @tab-click="updateTableData" type="card" @edit="removeTab"> <!-- <el-tab-pane v-for="(item , index) in modalForm.classifyList" :key="index" :label="item.materialName" :name="item.id">--> <el-tab-pane v-for="item in classifyList" :key="item.materialCode" :label="`${item.materialName} - ${item.materialCode}`" :name="item.materialCode" > <!-- <div v-for="(sub, sIdx) in item.subList" :key="sIdx" style="margin-bottom: 20px;"> --> <el-row > <el-col :span="24"> <el-table :key="contentTable" :data="item.subList" border> <el-table-column align="center" :label="$t('operation')" width="100"> <template slot-scope="scope"> <div class="el-zlw-operate"> <div> <a href="javascript:" @click="copysonList(item.subList, scope)"><i class="el-icon-copy-document" /></a> <a href="javascript:" @click="addsonList(item.subList, scope)"><i class="el-icon-circle-plus-outline" /></a> <a v-show="item.subList.length > 1" href="javascript:" @click="removesonList(item.subList, scope)"><i class="el-icon-remove-outline" /></a> </div> </div> </template> </el-table-column> <el-table-column align="center" type="index" :label="$t('no')" width="50" /> <!-- 修订前项次内容--> <el-table-column align="center" header-align="center"> <template slot="header"> <span class="required">{{ $t('qualitySystem.systemfile.beforeReviseItemContent') }}</span> </template> <template slot-scope="scope"> <el-form-item label-width="0" :prop="'subList.' + scope.$index + '.materialCode'" :rules="[{ required: true, message: $t('pleaseEnter', { param: $t('qualitySystem.systemfile.beforeReviseItemContent') }), trigger: 'blur' }]"> <el-input v-model="scope.row.materialCode" clearable type="textarea" :rows="2" maxlength="100" :placeholder="$t('pleaseEnter', { param: $t('qualitySystem.systemfile.beforeReviseItemContent') })" /> <!-- <span v-else>{{ scope.row.beforeReviseItemContent }}</span> --> </el-form-item> </template> </el-table-column> </el-table> </el-col> </el-row> </el-tab-pane> </el-tabs> </el-collapse-item> </el-collapse> </div> </el-form> </div> </template> <script> import { getSupplierClaimInfo, getSupplySqeByCode, getSupplyInfoByCode, getUserInfoByRoleCode, getSupplyInfoBySupplyCode, addSupplierClaim, updateSupplierClaim } from '@/api/spl/supplierClaim/api'; import { getSupplierList } from '@/api/baseData/supplier/api'; import { getMaterialMasterDataList } from '@/api/baseData/materialMasterData/api'; import { getDicts } from '@/api/baseData/dict/api'; import moment from 'moment'; import dataSelect from 'zlw/components/common/tool/dataSelect.vue' import viewSettings from "@/components/viewSettings"; function Form() { return { id: null,// 主键ID version: null,// 乐观锁 deleteFlag: null,// 删除标识 state: null,// 状态 formCode: null,// 编号 currentProcessorBy: null,// 当前流程处理人 currentProcessorId: null,// 当前流程处理人ID createDeptPathBy: null,// 创建部门 createDeptPathId: null,// 创建部门ID processFlow: null,// 处理流程 year: null,// 年度 code: null,// 编号 formState: null,// 表单状态 shortName: null,// 供方简称 materialCode: null,// 物料编码 materialName: null,// 物料名称 categoryId: null,// 供应商品类id categoryBy: null,// 供应商品类名称 applyDeptId: null,// 申请部门id applyDeptBy: null,// 申请部门名称 amount: null,// 索赔金额 applicatId: null,// 申请人id applicatBy: null,// 申请人名称 applicatTime: null,// 申请日期 source: null,// 来源 orderCode: null,// 订单号 quantityNum: null,// 数量 dealUrl: null,// 质量协议 description: null,// 异常描述 descriptionUrl: null,// 异常描述附件 hasReconciliationNumber: null,// 是否对账 hasReconciliationNumber1: null,// 是否对账 purchaserBy: null,// 采购人 purchaserBy1: null,// 采购人 purchaserByName: null, //采购人 workPeople: null,// 加工人数 workTime: null,// 加工时间 workPrice: null,// 小时单价 workTotal: null,// 加工费合计 matterName: null,// 材料名称 matterNum: null,// 材料损失费数量 matterPrice: null,// 损失费单价 matterTotal: null,// 材料损失费合计 delayNum: null,// 误工人数 delayTime: null,// 误工时间 delayPrice: null,// 误工单价 delayTotal: null,// 误工合计 otherIndemnity: null,// 其它客户赔款 otherPenalize: null,// 其它品质处罚 otherTotal: null,// 其它合计 parentId: null,// 父级id roles: null, // 角色 money: 1, amount: 1, backState: null, backIdea: null, supplierId: null, sqe: null, supplierName: null, sqeName: null, isOnline: '线上', otherTypes: null, otherTypesAmounts: null, classifyList:[new classifyListForm()], } } function classifyListForm() { return { id: null, parentId: null, materialCode: null, materialName: null, costList:[new costListForm()], } } function costListForm() { return { id: null, parentId: null, feeType: null, workPeople: null, workTime: null, } } export default { name: 'SupplierClaimCreateOrEdit', components: { dataSelect, viewSettings }, data() { return { // 表单数据 modalForm: new Form(), // 所有选中的物料及其子表单数据 classifyList: [], activeTab: '', getSupplierList, getMaterialMasterDataList, // 标题 title: '供应商索赔确认单', sonObj: {}, // 表单参数 defaultProps: { children: 'children', label: 'label' }, userinfo: {}, select: null, propsBpm: null, rules: { // 校验 year: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.year') }), trigger: 'blur' }], //年度 code: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.code') }), trigger: 'blur' }], //编号 //表单状态 formState: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.formState') }), trigger: 'blur' }], //供方简称 shortName: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.shortName') }), trigger: 'blur' }], //供应商品类id categoryId: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.categoryId') }), trigger: 'blur' }], //供应商品类名称 categoryBy: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.categoryBy') }), trigger: 'blur' }], //申请部门id applyDeptId: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.applyDeptId') }), trigger: 'blur' }], //申请部门名称 applyDeptBy: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.applyDeptBy') }), trigger: 'blur' }], //物料名称 materialName: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.materialName') }), trigger: 'blur' }], //物料编码 materialCode: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.materialCode') }), trigger: 'blur' }], amount: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.amount') }), trigger: 'blur' }], //索赔金额 //申请人id applicatId: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.applicatId') }), trigger: 'blur' }], //SQE sqe: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('SQE') }), trigger: 'blur' }], //申请日期 applicatTime: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.applicatTime') }), trigger: 'blur' }], source: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.source') }), trigger: 'blur' }], //来源 orderCode: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.orderCode') }), trigger: 'blur' }],//订单号 quantityNum: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.quantityNum') }), trigger: 'blur' }], //数量 dealUrl: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.dealUrl') }), trigger: 'blur' }], //质量协议 //异常描述 description: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.description') }), trigger: 'blur' }], //异常描述附件 descriptionUrl: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.descriptionUrl') }), trigger: 'blur' }], // 是否对账 hasReconciliationNumber: [ { required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.hasReconciliationNumber') }), trigger: ['change'] } ], hasReconciliationNumber1: [{ required: true, message: '请选择是否对账', trigger: 'change' }], // 是否对账 // 采购人 purchaserBy: [{ required: true, message: '请选择采购人', trigger: 'change' }], purchaserBy1: [{ required: true, message: '请选择采购人', trigger: 'change' }], purchaserByName: [{ required: true, message: '请选择采购人', trigger: 'change' }], //加工人数 workPeople: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.workPeople') }), trigger: 'blur' }], //加工时间 workTime: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.workTime') }), trigger: 'blur' }], //小时单价 workPrice: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.workPrice') }), trigger: 'blur' }], //加工费合计 workTotal: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.workTotal') }), trigger: 'blur' }], //材料名称 matterName: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.matterName') }), trigger: 'blur' }], //材料损失费数量 matterNum: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.matterNum') }), trigger: 'blur' }], //损失费单价 matterPrice: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.matterPrice') }), trigger: 'blur' }], //材料损失费合计 matterTotal: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.matterTotal') }), trigger: 'blur' }], //误工人数 delayNum: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.delayNum') }), trigger: 'blur' }], //误工时间 delayTime: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.delayTime') }), trigger: 'blur' }], //误工单价 delayPrice: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.delayPrice') }), trigger: 'blur' }], //误工合计 delayTotal: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.delayTotal') }), trigger: 'blur' }], //其它客户赔款 otherIndemnity: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.otherIndemnity') }), trigger: 'blur' }], //其它品质处罚 otherPenalize: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.otherPenalize') }), trigger: 'blur' }], //其它合计 otherTotal: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.otherTotal') }), trigger: 'blur' }], //父级id parentId: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.parentId') }), trigger: 'blur' }], //合计 totalBy: [{ required: false, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.totalBy') }), trigger: ['blur', 'change'] }], backState: [{ required: true, message: this.$t('pleaseEnter', { param: '供应商回签' }), trigger: ['blur', 'change'] }], backIdea: [{ required: true, message: this.$t('pleaseEnter', { param: '供应商回签意见' }), trigger: 'blur' }], otherTypesAmounts: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.otherTypesAmounts') }), trigger: 'blur' }], otherTypes: [{ required: true, message: this.$t('pleaseEnter', { param: this.$t('spl.supplierClaim.otherTypes') }), trigger: 'blur' }], }, activeNames: ['1', '2'], buyList: [], buyer: null, roleId: 0, sqeList: [],//sqe角色人员 } }, computed: { optionsDisabled() { return { disabledDate: (time) => { return time.getTime() < Date.now() - 8.64e7 } } }, parentComponent() { return this } }, created() { // 获取当前登录人信息 this.getUserProfile().then((res) => { this.userinfo = res.data; }) if (this.$store.getters.roles.includes('SQE')) { this.roleId = 1 this.getUserInfoByRoleCode(); } if (JSON.parse(this.$route.query.dataForm).id) { // 查询表单信息 getSupplierClaimInfo(JSON.parse(this.$route.query.dataForm).id).then((res) => { this.modalForm = Object.assign(new Form(), res.data) if (this.modalForm.state = "未提交") { // 设置发起人角色 if (this.$store.getters.roles.includes('PD')) { this.modalForm.roles = 'PD' } if (this.$store.getters.roles.includes('CQE')) { this.modalForm.roles = 'CQE' } if (this.$store.getters.roles.includes('SQE')) { this.modalForm.roles = 'SQE' } // 售后进来的没有sqe if (!this.modalForm.sqe) { if (this.modalForm.roles == 'SQE') { this.modalForm.roles = 'SQE' this.roleId = 1 // console.log(this.userinfo, "角色------------------》") // 获取当前登录人信息 this.getUserProfile().then((res) => { this.userinfo = res.data; this.modalForm.sqe = JSON.stringify([{ account: this.userinfo.userName, name: this.userinfo.nickName, id: this.userinfo.userId }]) }) } if (this.modalForm.roles == 'PD') { this.getSQEList() } if (this.modalForm.roles == 'CQE') { let arr = [] if (this.modalForm.supplierCode) { getSupplySqeByCode(this.modalForm.supplierCode).then(ress => { // 过滤空元素(空对象) const filtered = ress.filter(item => Object.keys(item).length > 0 // 过滤掉空对象 {} ); // 根据id去重(保留首次出现的元素) filtered.map(item => { arr.push(item.qaEngineer) }) let parsedArray = arr.flatMap(str => JSON.parse(str)); let ids = new Set(); let uniqueArray = []; parsedArray.forEach(item => { if (!ids.has(item.id)) { ids.add(item.id); uniqueArray.push(item); } }); if (uniqueArray.length > 0) { this.modalForm.sqe = JSON.stringify(uniqueArray); } else { this.$message.warning('请先维护该供应商对应的质量工程!') } }) } else { this.$message.warning('历史数据没查询到对应的供应商编码信息,请重新选择下供应商!') } } } } // console.log(this.modalForm.roles, 1233333) // 以前代码逻辑定义的,怕改错,暂保留下 if (this.modalForm.roles == 'SQE') { this.roleId = 1 } if (this.modalForm.roles == 'PD' && this.modalForm.sqe) { this.modalForm.sqeName = JSON.parse(this.modalForm.sqe)[0].name this.getSQEList() } // 比较金额大小 if (this.modalForm.totalByStr) { let code = 'gztyre_gyssp'; getDicts(code).then(res => { this.money = res.data?.[0]?.dicName if (parseFloat(this.modalForm.totalByStr) >= this.money * 1) { // console.log(parseFloat(this.modalForm.totalByStr), 222222) this.modalForm.amount = 1 console.log('大于合计金额:', this.modalForm.amount) } else { this.modalForm.amount = 2 console.log('小于合计金额:', this.modalForm.amount) } }) } }) } else { setTimeout(() => { if (this.$store.getters.roles.includes('PD')) { this.modalForm.roles = 'PD' this.getSQEList() } if (this.$store.getters.roles.includes('CQE')) { this.modalForm.roles = 'CQE' } if (this.$store.getters.roles.includes('SQE')) { this.modalForm.roles = 'SQE' this.roleId = 1 console.log(this.userinfo, "角色------------------》") // 获取当前登录人信息 this.getUserProfile().then((res) => { this.userinfo = res.data; this.modalForm.sqe = JSON.stringify([{ account: this.userinfo.userName, name: this.userinfo.nickName, id: this.userinfo.userId }]) }) } console.log(this.modalForm.roles, "角色") // this.modalForm.purchaserBy = this.modalForm.purchaserBy1; }, 300) } }, methods: { /** 提交按钮(数据权限) */ submitForm() { this.modalForm.isSubmit = 1 this.modalForm.state = '已提交' // 必填验证 this.$refs.modelForm.validate ((valid) => { if (valid) { this.save() } else { this.$setScroll() } }) }, /** 保存按钮(数据权限) */ saveForm() { this.modalForm.isSubmit = 0 this.modalForm.state = '未提交' this.save() }, /** 通用保存方法 */ save() { if (this.modalForm.id != null && undefined != this.modalForm.id) { updateSupplierClaim(this.modalForm).then(res => { if (res.code == 200) { this.$message({ type: 'success', message: this.$t('updateSuccess') }) } else { this.$message({ type: 'error', message: this.$t('modifyFailed') }) } this.closeModal(res.data) }).catch(() => { this.loadingType(false) }) } else { addSupplierClaim(this.modalForm).then(res => { if (res.code == 200) { this.$message({ type: 'success', message: this.$t('saveSuccess') }) } else { this.$message({ type: 'error', message: this.$t('saveFailed') }) } this.closeModal(res.data) }).catch(() => { this.loadingType(false) }) } }, closeModal(id) { this.loadingType(false) if (this.modalForm.isSubmit == 1) { // 暂存不关闭弹窗 this.closeBtn('close') } else { // 期望暂存接口返回id用于查询数据 getSupplierClaimInfo(id).then(res => { this.modalForm = Object.assign(new Form(), res.data) }) } }, // 选择是否对账 handleChange(value) { this.modalForm.hasReconciliationNumber = value this.modalForm.hasReconciliationNumber1 = value; }, handleChange1(value) { this.modalForm.hasReconciliationNumber = value; this.modalForm.hasReconciliationNumber1 = value; }, // 查询采购人员角色用户 getUserInfoByRoleCode() { getUserInfoByRoleCode('spl_buy').then(res => { let arr = res.data; arr.map(item => { let obj = {}; obj.label = item.nickName; obj.value = [{ account: item.userName, id: item.userId, name: item.nickName, parentDeptId: item.dept.deptId, parentDeptName: item.dept.deptName, selected: false, type: 'user', userName: item.userName }] this.buyList.push(obj); }) }) }, // 获取sqe角色人员 getSQEList() { getUserInfoByRoleCode('SQE').then(res => { let arr = res.data; arr.map(item => { let obj = {}; obj.label = item.nickName; obj.value = [{ account: item.userName, id: item.userId, name: item.nickName, parentDeptId: item.dept?.deptId, parentDeptName: item.dept?.deptName, selected: false, type: 'user', userName: item.userName }] this.sqeList.push(obj); }) console.log('this.sqeList:', this.sqeList); }) }, // 选择采购 buyChange(val) { if (val) { console.log('采购人员:', val) const list = this.buyList.filter(item => { return item.label === val }) // console.log(list,11111) this.modalForm.purchaserBy = list[0]?.value } else { this.modalForm.purchaserBy = null } }, buyChange1(val) { if (val) { console.log('采购人员:', val) const list = this.buyList.filter(item => { return item.label === val }) // console.log(list,11111) this.modalForm.purchaserBy = list[0]?.value } else { this.modalForm.purchaserBy = null } }, // 选择sqe sqeChange(val) { if (val) { // console.log('sqe:', val) const list = this.sqeList.filter(item => { return item.label === val }) // console.log(list,11111) this.modalForm.sqe = JSON.stringify(list[0]?.value) console.log('this.modalForm.sqe:', this.modalForm.sqe) } else { this.modalForm.sqe = null } }, /** 动态添加行 复制方法 */ copyTableRow(lists, scope) { let son = JSON.parse(JSON.stringify(scope.row)) lists.splice(scope.$index, 0, son) }, /** 动态添加行 增加方法 */ addTableRow(lists, scope) { let son = Object.assign({}, this.modalFormSonObj) lists.splice(scope.$index + 1, 0, son) lists.map((item, index) => { item.seqNo = index + 1 // 更改序号 }) }, /** 动态添加行 删除方法 */ removeTableRow(lists, scope) { for (let i = 0; i < lists.length; i++) { if (i === scope.$index) { lists.splice(i, 1) } } lists.map((item, index) => { item.seqNo = index + 1 // 更改序号 }) }, // 选择人员回调 selected(select) { this.select = Object.assign([], select); }, /** 手动再验证 */ validateField(type) { this.$refs.modelForm.validateField(type) }, /** 格式化时间 */ checkDate(val) { return moment(val).format('yyyy-MM-DD') }, /** 格式化时间 */ checkDateTime(val) { return moment(val).format('YYYY-MM-DD HH:mm:ss') }, // 工作流节点选择 workChange() { const { workPeople, workTime, workPrice, matterNum, matterPrice, delayNum, delayTime, delayPrice, otherIndemnity, otherPenalize, otherTypesAmounts } = this.modalForm if (workPeople && workTime && workPrice) { const workTotal = Number(workPeople) * Number(workTime) * Number(workPrice) this.modalForm.workTotal = parseFloat(workTotal.toFixed(2)); } if (matterNum && matterPrice) { const matterTotal = Number(matterNum) * Number(matterPrice); this.modalForm.matterTotal = parseFloat(matterTotal.toFixed(2)); } if (delayNum && delayTime && delayPrice) { const delayTotal = Number(delayNum) * Number(delayTime) * Number(delayPrice); this.modalForm.delayTotal = parseFloat(delayTotal.toFixed(2)); } if (otherIndemnity && otherPenalize && otherTypesAmounts) { const otherTotal = Number(otherIndemnity) + Number(otherPenalize) + Number(otherTypesAmounts);; this.modalForm.otherTotal = parseFloat(otherTotal.toFixed(2)); } this.deductionChange() }, // 扣除改变 deductionChange() { const { workTotal, matterTotal, delayTotal, otherTotal } = this.modalForm // if (workTotal && matterTotal && delayTotal && otherTotal) {总扣除金额} let totalVal = Number(workTotal) + Number(matterTotal) + Number(delayTotal) + Number(otherTotal) this.modalForm.totalBy = this.numToChinese(totalVal); this.modalForm.totalByStr = totalVal; }, // 数字转中文 numToChinese(num) { const fraction = ['角', '分']; const digit = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']; const unit = [['元', '万', '亿'], ['', '拾', '佰', '仟']]; let head = num < 0 ? '欠' : ''; num = Math.abs(num); let s = ''; for (let i = 0; i < fraction.length; i++) { s += (digit[Math.floor(num * 10 * Math.pow(10, i)) % 10] + fraction[i]).replace(/零./, ''); } s = s || '整'; num = Math.floor(num); for (let i = 0; i < unit[0].length && num > 0; i++) { let p = ''; for (let j = 0; j < unit[1].length && num > 0; j++) { p = digit[num % 10] + unit[1][j] + p; num = Math.floor(num / 10); } s = p.replace(/(.)*零$/, '').replace(/^$/, '零') + unit[0][i] + s; } return head + s.replace(/(.)*零元/, '元').replace(/(.)+/g, '零').replace(/^整$/, '零元整'); }, // 供应商主数据弹窗 doGetMain(list, row) { this.modalForm.supplierCode = list[0].supplierCode; this.modalForm.supplierName = list[0].supplierName; this.modalForm.shortName = row.supplierName; if (this.modalForm.supplierCode) { // if (this.modalForm.roles == 'CQE') { // let arr = []; // getSupplySqeByCode(this.modalForm.supplierCode).then(ress => { // // 过滤空元素(空对象) // const filtered = ress.filter(item => // Object.keys(item).length > 0 // 过滤掉空对象 {} // ); // // 根据id去重(保留首次出现的元素) // filtered.map(item => { arr.push(item.qaEngineer) }) // let parsedArray = arr.flatMap(str => JSON.parse(str)); // let ids = new Set(); // let uniqueArray = []; // parsedArray.forEach(item => { // if (!ids.has(item.id)) { // ids.add(item.id); // uniqueArray.push(item); // } // }); // if (uniqueArray.length > 0) { // this.modalForm.sqe = JSON.stringify(uniqueArray); // } else { // this.modalForm.shortName = null // this.modalForm.supplierCode = null // this.$message.warning('请先维护该供应商对应的质量工程!') // } // }) // } // 获取供应商信息 getSupplyInfoByCode(this.modalForm.supplierCode).then(res => { if (res) { this.modalForm.supplierId = JSON.stringify(res); } else { this.$message.error("请联系管理员去维护供应商应答人员信息!"); } }) // 判断供应商类型 getSupplyInfoBySupplyCode(this.modalForm.supplierCode).then(res => { if (res.managementType) { this.modalForm.isOnline = res.managementType; } else { this.$message.error("请联系管理员去维护供应商线上/线下信息!"); } }) console.log(this.modalForm.sqe, '------------>'); } }, doGetMainPd(list, row) { this.modalForm.supplierCode = list[0].supplierCode; this.modalForm.supplierName = list[0].supplierName; this.modalForm.shortName = row.supplierName; if (this.modalForm.supplierCode) { // let arr = []; // getSupplySqeByCode(this.modalForm.supplierCode).then(ress => { // // 过滤空元素(空对象) // const filtered = ress.filter(item => // Object.keys(item).length > 0 // 过滤掉空对象 {} // ); // // 根据id去重(保留首次出现的元素) // filtered.map(item => { arr.push(item.qaEngineer) }) // let parsedArray = arr.flatMap(str => JSON.parse(str)); // let ids = new Set(); // let uniqueArray = []; // parsedArray.forEach(item => { // if (!ids.has(item.id)) { // ids.add(item.id); // uniqueArray.push(item); // } // }); // this.modalForm.sqe = uniqueArray; // console.log("SQE信息:", this.modalForm.sqe); // }) // 获取供应商信息 // getSupplyInfoByCode(this.modalForm.supplierCode).then(res => { // if (res) { // this.modalForm.supplierId = JSON.stringify(res); // } else { // this.$message.error("请联系管理员去维护供应商应答人员信息!"); // } // }) // // 判断供应商类型 // getSupplyInfoBySupplyCode(this.modalForm.supplierCode).then(res => { // if (res.managementType) { // this.modalForm.isOnline = res.managementType; // } else { // this.$message.error("请联系管理员去维护供应商线上/线下信息!"); // } // }) console.log(this.modalForm.sqe, '------------>'); } }, // 获取物料信息 doGetMaterial(list, row) { this.classifyList = []; this.activeTab = ''; if (list && list.length) { this.modalForm.materialCode = list.map(item => item.materialCode).join(',') list.forEach(item => { // 初始化该物料对应的 subList 及其 sonList const subList = [ { parentId: '',workPeople: '',workTime: '',feeType: '' } ]; this.classifyList.push({ materialName: item.materialName, materialCode: item.materialCode, subList // 每个物料自带自己的子表单结构 }); }); // 默认激活第一个 tab if (this.classifyList.length > 0) { this.activeTab = this.classifyList[0].materialCode; } } else { this.modalForm.materialCode = null this.modalForm.materialName = null } }, // 添加一个新的孙级到指定子级 addSon(subItem) { subItem.sonList.push({ materialname: '' }); }, /** 导出word按钮操作 */ handleExportWord() { this.modalForm.titleWord = this.title; this.exportDownWord2Pdf({ templatePath: "templates/word/supplyClaim.docx", data: this.modalForm }, this.title); }, formatNumber(num) { if (num || num == 0) { const parts = num.toString().split('.'); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ','); return '¥' + Number(num).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',') + '元'; } }, /** 手动再验证 */ validateField(type) { this.$refs.modelForm.validateField(type) }, // 去除输入框中的空格 descriptionChange() { this.modalForm.description = this.modalForm.description.replace(/\s/g, '') console.log(this.modalForm.description, '======>>>>>>>>'); }, } } </script> <style lang="scss" scoped> [class^=el-icon-copy-document] { color: #3370FF; background-color: transparent; } [class^=el-icon-circle-plus-outline] { color: #3370FF; background-color: transparent; } [class^=el-icon-remove-outline] { color: #F54A4F; background-color: transparent; } .el-zlw-operate { color: #606266; font-size: 1.5em; vertical-align: middle; } .el-zlw-operate>div>a>i { margin: 0 3px; } .date_picker { width: 100px !important; } .Twoheader { display: flex; justify-content: space-between; width: 100%; div { text-align: center } .red { padding-right: 5px; color: #F54A4F; } } .Twoheader>div { text-align: center } ::v-deep .el-collapse-item__content { padding-bottom: 5px; } ::v-deep .no-scroll-x { overflow-x: hidden !important; } ::v-deep .no-scroll-x .cell { overflow: hidden !important; } ::v-deep .no-scroll-x .el-table__body-wrapper { overflow-x: hidden !important; } ::v-deep.no-scroll .el-table__body-wrapper { overflow-x: hidden !important; } ::v-deep.no-scroll .el-table__body { overflow: hidden !important; } ::v-deep.no-scroll--scrollable-x .el-table__body-wrapper { overflow-x: hidden !important; } .col_right_border { border-right: 1px solid #e8e8e8; } .fy_title_box { height: 30px; line-height: 30px; text-align: center; background: #f8f8f9; } .required::before { position: absolute; top: 50%; left: 0; height: 8px; margin-top: -13px; color: #F54A4F; content: "*"; } </style>
最新发布
09-19
<template> <view class="container"> <!-- 背景视频 --> <!-- 双视频缓冲区 --> <video v-if="videoBuffers[0]" v-show="currentVideoIndex === 0" :src="videoBuffers[0]" autoplay loop muted class="background-video" :class="{ 'video-active': currentVideoIndex === 0 }" @loadeddata="onVideoLoaded(0)" ref="video0" /> <video v-if="videoBuffers[1]" v-show="currentVideoIndex === 1" :src="videoBuffers[1]" autoplay loop muted class="background-video" :class="{ 'video-active': currentVideoIndex === 1 }" @loadeddata="onVideoLoaded(1)" ref="video1" /> <!-- 回显数据展示区域 --> <view class="message-container"> <view v-for="(message, index) in messages" :key="index" :class="{ 'user-css': message.isUser }" class="message-item"> <text class="message-text" :class="{ 'user-message': message.isUser }">{{ message.text }}</text> </view> </view> <!-- 底部操作栏 --> <view class="bottom-bar"> <view class="bottom-input"> <!-- 文本输入框 --> <input type="text" v-model="inputText" placeholder="说任何东西..." class="input-box" /> <!-- 发送按钮 --> <button @click="sendText" class="send-btn"> <text class="send-text">→</text> </button> </view> <!-- 录音按钮 --> <image :src="isRecording ? '/static/removeRm.png' : '/static/starRm.png'" mode="" @click="toggleRecording" class="record-btn"></image> </view> <!-- 指示器 --> <view v-if="isRecording" class="recording-indicator"> <view class="recording-circle"></view> </view> </view> </template> <script> import { Recorder } from 'opus-recorder'; export default { data() { return { isRecording: false, audioChunks: [], recorder: null, stream: null, socketTask: null, isConnected: false, reconnectAttempts: 0, maxReconnectAttempts: 5, reconnectDelay: 1000, inputText: "", // 输入的文本 messages: [], // 存储所有消息的数组 recorderManager: null, // 录音管理器 // 视频缓冲相关 videoBuffers: ["", ""], // 双缓冲区 currentVideoIndex: 0, // 当前显示的视频索引 videoMap: { ['开心']: "../../static/RoleA-00.mp4", ['你好呀']: "../../static/RoleA-01.mp4", ['有什么我可以帮你的吗?']: "../../static/RoleA-01.mp4", default: "../../static/RoleA-00.mp4" }, preloadedVideos: new Map(), // 预加载的视频缓存 isTransitioning: false, // 是否正在切换 videosLoaded: false, // 视频是否初始化完成 currentSessionId: 0, // 当前会话ID,用于区分不同的对话轮次 lastProcessedMessages: new Set(), // 存储当前会话中已处理的消息 autoScroll: true, // 控制是否自动滚动 isUserScrolling: false, // 用户是否正在手动滚动 maxMessages: 50, // 限制消息数量 messageCleanupThreshold: 100, // 清理阈值 }; }, onLoad() { // 页面加载时建立 WebSocket 连接 this.connectWebSocket(); // #ifdef APP-PLUS this.recorderManager = uni.getRecorderManager(); this.recorderManager.onStop((res) => { console.log("录音停止,文件路径:", res.tempFilePath); // 读取文件并发送二进制数据 uni.getFileSystemManager().readFile({ filePath: res.tempFilePath, success: (r) => { this.sendAudioChunk(r.data); }, fail: (err) => console.error("读取音频文件失败", err), }); this.sendListenStopMessage(); }); // #endif // 初始化视频系统 this.initVideoSystem(); // 监听用户滚动行为 this.addScrollListener(); }, methods: { // 初始化视频系统 async initVideoSystem() { try { // 立即设置初始视频 const initialVideo = this.videoMap["default"]; this.videoBuffers[0] = initialVideo; this.videosLoaded = true; // 在后台预加载其他视频 setTimeout(() => { this.preloadAllVideos(); }, 100); console.log("视频系统初始化完成"); } catch (error) { console.error("视频系统初始化失败:", error); } }, // 视频预加载策略 async preloadAllVideos() { const videoUrls = [...new Set(Object.values(this.videoMap))]; // 使用Promise.allSettled而不是for循环,提高并发性 const results = await Promise.allSettled( videoUrls.map(url => this.preloadSingleVideo(url)) ); // 记录失败的视频 const failed = results.filter(r => r.status === 'rejected'); if (failed.length > 0) { console.warn(`${failed.length}个视频预加载失败`); } console.log(`视频预加载完成: ${results.length - failed.length}/${results.length}`); }, // 预加载单个视频 preloadSingleVideo(url) { return new Promise((resolve, reject) => { if (this.preloadedVideos.has(url)) { resolve(); return; } // #ifdef H5 const video = document.createElement('video'); video.src = url; video.preload = "auto"; video.muted = true; video.loop = true; video.addEventListener('canplaythrough', () => { this.preloadedVideos.set(url, video); resolve(); }); video.addEventListener('error', (e) => { console.error(`视频预加载失败: ${url}`, e); reject(e); }); video.load(); // #endif // #ifdef APP-PLUS // 小程序端直接标记为已预加载 this.preloadedVideos.set(url, true); resolve(); // #endif }); }, // 设置视频URL(简化版本) setVideoUrl(scene) { if (this.isTransitioning) return; const newVideoUrl = this.videoMap[scene] || this.videoMap["default"]; const currentUrl = this.videoBuffers[this.currentVideoIndex]; if (currentUrl === newVideoUrl) return; this.isTransitioning = true; // 如果当前只有一个视频,直接替换 if (!this.videoBuffers[1 - this.currentVideoIndex]) { this.videoBuffers[1 - this.currentVideoIndex] = newVideoUrl; // 等待 DOM 更新后切换 this.$nextTick(() => { setTimeout(() => { this.currentVideoIndex = 1 - this.currentVideoIndex; this.isTransitioning = false; }, 300); }); } else { // 双缓冲切换 this.smoothTransition(newVideoUrl); } }, // 平滑过渡动画(简化版) smoothTransition(newVideoUrl) { const nextIndex = 1 - this.currentVideoIndex; this.videoBuffers[nextIndex] = newVideoUrl; // 强制更新 DOM this.$forceUpdate(); // 延迟切换,确保过渡动画完成 setTimeout(() => { this.currentVideoIndex = nextIndex; this.isTransitioning = false; }, 300); // 过渡动画时长 }, // 视频加载完成 onVideoLoaded(index) { console.log(`视频${index}加载完成`); }, // 建立 WebSocket 连接 connectWebSocket() { const wsUrl = "ws://106.13.215.232:8000/aiservers/v1/?device-id=1A:34:B4:02:5D:F6&client-id=web_test_client"; // 替换为你的 WebSocket 服务器地址 this.socketTask = uni.connectSocket({ url: wsUrl, success: () => { console.log("WebSocket连接成功"); }, fail: (err) => { console.error("WebSocket连接失败:", err); } }); // 监听 WebSocket 连接打开事件 uni.onSocketOpen((res) => { this.isConnected = true; console.log("WebSocket连接已打开:", res); this.sendHello(); }); // 监听 WebSocket 接收到消息事件 uni.onSocketMessage((res) => { this.handleMessage(res); }); // 监听 WebSocket 连接关闭事件 uni.onSocketClose((res) => { this.isConnected = false; console.log("WebSocket连接已关闭:", res); if (this.reconnectAttempts < this.maxReconnectAttempts) { setTimeout(() => { this.reconnectAttempts++; console.log( `尝试重连... (${this.reconnectAttempts}/${this.maxReconnectAttempts})`); this.connectWebSocket(); }, this.reconnectDelay * this.reconnectAttempts); } else { this.addMessage("系统消息", "连接已断开,请刷新页面重试", false); } }); // 监听 WebSocket 错误事件 uni.onSocketError((res) => { console.error("WebSocket发生错误:", res); }); }, // 发送握手消息 sendHello() { const message = { type: "hello", device_id: "1A:34:B4:02:5D:F6", device_name: "UniApp设备", device_mac: "1A:34:B4:02:5D:F6", token: "your-token1", features: { mcp: true } }; uni.sendSocketMessage({ data: JSON.stringify(message), success: () => { console.log("握手消息发送成功"); }, fail: (err) => { console.error("握手消息发送失败:", err); } }); }, // 发送文本消息 sendText() { if (!this.isConnected) { this.addMessage("系统消息", "请先建立 WebSocket 连接", false); return; } // 开始新的会话轮次 this.startNewSession(); const message = { type: "listen", mode: "manual", state: "detect", text: this.inputText }; uni.sendSocketMessage({ data: JSON.stringify(message), success: () => { console.log("文本消息发送成功"); this.addMessage("用户", this.inputText, true); }, fail: (err) => { console.error("文本消息发送失败:", err); this.addMessage("系统消息", "消息发送失败", false); } }); this.inputText = ""; // 清空输入框 }, // 滚动监听器 addScrollListener() { this.$nextTick(() => { // #ifdef H5 const messageContainer = document.querySelector('.message-container'); if (messageContainer) { messageContainer.addEventListener('scroll', this.handleScroll); } // #endif }); }, // 处理滚动事件 handleScroll(event) { const container = event.target; const isAtBottom = container.scrollTop + container.clientHeight >= container.scrollHeight - 10; // 如果用户滚动到底部,启用自动滚动 // 如果用户向上滚动,暂停自动滚动 this.autoScroll = isAtBottom; }, // 处理服务器返回的消息 handleMessage(res) { if (typeof res.data === 'string') { try { const jsonMessage = JSON.parse(res.data); this.handleJsonMessage(jsonMessage); } catch (error) { console.error('解析 JSON 失败:', error); } } else if (res.data instanceof ArrayBuffer) { this.handleAudioData(res.data); } }, // 处理 JSON 消息 handleJsonMessage(message) { switch (message.type) { case 'stt': // this.addMessage("语音识别", message.text, false); break; case 'llm': this.addMessage("回复消息", message.text, false); break; case 'tts': if ((message.state === 'sentence_start' || message.state === 'sentence_end') && message.text !== undefined) { this.addMessage("语音合成", message.text, false); this.setVideoUrl(message.text) } break; default: console.log('未知类型消息:', message); } }, // 添加消息 addMessage(name, text, isUser) { // 为每个消息创建唯一标识符 const messageKey = `${this.currentSessionId}-${name}-${text}-${isUser}`; // 检查是否是同一会话中的重复消息 if (this.lastProcessedMessages.has(messageKey)) { console.log("同一会话中的重复消息,未添加:", text); return; } // 添加到已处理消息集合 this.lastProcessedMessages.add(messageKey); // 添加消息到数组 this.messages.push({ name, text, isUser, time: new Date().toLocaleTimeString(), sessionId: this.currentSessionId // 添加会话ID标识 }); console.log("消息已添加:", text); // 只有在自动滚动启用时才滚动到底部 if (this.autoScroll) { this.scrollToBottom(); } // 内存管理:限制消息数量 if (this.messages.length > this.messageCleanupThreshold) { this.messages.splice(0, this.messages.length - this.maxMessages); // 同时清理对应的已处理消息记录 this.cleanupProcessedMessages(); } }, cleanupProcessedMessages() { // 只保留最近的消息标识符 const recentMessages = new Set(); this.messages.slice(-this.maxMessages).forEach(msg => { const key = `${msg.sessionId}-${msg.name}-${msg.text}-${msg.isUser}`; recentMessages.add(key); }); this.lastProcessedMessages = recentMessages; }, // 滚动到底部 scrollToBottom() { // 使用 nextTick 确保 DOM 更新完成后再滚动 this.$nextTick(() => { // #ifdef H5 const messageContainer = document.querySelector('.message-container'); if (messageContainer) { messageContainer.scrollTop = messageContainer.scrollHeight; } // #endif // #ifdef APP-PLUS || MP-WEIXIN const query = uni.createSelectorQuery().in(this); query.select('.message-container').boundingClientRect((rect) => { if (rect) { uni.pageScrollTo({ selector: '.message-container', scrollTop: rect.height, duration: 300 // 平滑滚动动画时间 }); } }).exec(); // #endif }); }, // 处理音频数据 handleAudioData(arrayBuffer) { // this.playAudio(arrayBuffer); }, // 播放音频数据 playAudio(arrayBuffer) { const audioContext = new(window.AudioContext || window.webkitAudioContext)(); audioContext.decodeAudioData(arrayBuffer, (audioBuffer) => { const source = audioContext.createBufferSource(); source.buffer = audioBuffer; source.connect(audioContext.destination); source.start(); }); }, // 切换录音状态 toggleRecording() { this.isRecording ? this.stopRecording() : this.startRecording(); }, // 开始录音 async startRecording() { // 开始新的会话轮次 this.startNewSession(); // #ifdef APP-PLUS if (!this.recorderManager) { this.addMessage("系统消息", "当前平台不支持原生录音", false); return; } this.recorderManager.start({ duration: 60000, sampleRate: 16000, numberOfChannels: 1, encodeBitRate: 96000, format: "aac", frameSize: 50, }); this.isRecording = true; this.sendListenStartMessage(); return; // #endif // #ifdef H5 try { // 1. 获取麦克风权限 this.stream = await navigator.mediaDevices.getUserMedia({ audio: true }); // 2. 创建录音器并配置 this.recorder = new MediaRecorder(this.stream, { mimeType: 'audio/webm;codecs=opus', audioBitsPerSecond: 16000 }); this.recorder.ondataavailable = async (event) => { if (event.data && event.data.size > 0) { // 转换为ArrayBuffer发送 const buffer = await event.data.arrayBuffer(); if (this.isRecording) { this.sendAudioChunk(buffer); } } }; // 4. 录音停止时的清理 this.recorder.onstop = () => { this.stream.getTracks().forEach(track => track.stop()); this.sendListenStopMessage(); }; // 5. 开始录音(每100ms发送一次数据块) this.recorder.start(200); this.isRecording = true; // 6. 通知服务器开始接收音频 this.sendListenStartMessage(); } catch (error) { //TODO handle the exception console.error('录音启动失败:', error); } // #endif }, // 开始新会话 startNewSession() { this.currentSessionId++; this.lastProcessedMessages.clear(); console.log("开始新会话,会话ID:", this.currentSessionId); }, // 停止录音 stopRecording() { // #ifdef APP-PLUS this.recorderManager.stop(); this.isRecording = false; return; // #endif // H5 浏览器端停止 // #ifdef H5 this.recorder.stop(); this.stream.getTracks().forEach((t) => t.stop()); this.isRecording = false; // #endif }, // 发送开始录音消息 sendListenStartMessage() { if (!this.isConnected) return; const msg = { type: "listen", mode: "manual", state: "start" }; uni.sendSocketMessage({ data: JSON.stringify(msg), success: () => console.log("已通知服务器开始录音"), fail: (err) => console.error("开始录音消息发送失败:", err) }); }, // 告诉服务器:结束语音流 sendListenStopMessage() { if (!this.isConnected) return; const msg = { type: "listen", mode: "manual", state: "stop" }; uni.sendSocketMessage({ data: JSON.stringify(msg), success: () => console.log("已通知服务器停止录音"), fail: (err) => console.error("停止录音消息发送失败:", err) }); }, // 实时发送音频数据 sendAudioChunk(buffer) { if (!this.isConnected) return; uni.sendSocketMessage({ data: buffer, success: () => {}, fail: (err) => { console.error("音频数据发送失败:", err); } }); } }, // 页面卸载时关闭 WebSocket 连接 onUnload() { if (this.socketTask) { uni.closeSocket(); console.log("关闭 WebSocket 连接"); } if (this.stream) { this.stream.getTracks().forEach(track => track.stop()); } } }; </script> <style> .container { position: relative; width: 100%; height: 100vh; overflow: hidden; } .background-video { position: fixed; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; z-index: -1; opacity: 1; /* 直接设置为1,简化逻辑 */ transition: opacity 0.3s ease-in-out; animation: fade 0.3s forwards; } @keyframes fade { from { opacity: 0; } to { opacity: 1; } } .background-video.loaded { opacity: 1; /* 加载完成后变为不透明 */ } .message-container { position: absolute; top: 90px; width: min(70%, 400px); /* 限制最大宽度 */ right: 20px; max-height: calc(100vh - 180px); /* 动态计算高度 */ overflow-y: auto; z-index: 2; scroll-behavior: smooth; } /* 移动端适配 */ /* @media (max-width: 768px) { .message-container { width: calc(100% - 40px); right: 20px; left: 20px; } } */ .message-container::-webkit-scrollbar { width: 0px; } .message-container::-webkit-scrollbar-track { background: transparent; } .message-container::-webkit-scrollbar-thumb { background-color: transparent; border-radius: 0px; } .message-container::-webkit-scrollbar-thumb:hover { background-color: transparent); } .message-item { margin: 0 10px; } .message-text { padding: 10px 15px; border-radius: 18px; font-size: 14px; line-height: 1.4; word-wrap: break-word; max-width: 80%; display: inline-block; margin-bottom: 8px; } .user-css { text-align: end; } .user-message { background-color: #4CAF50; color: white; } .message-text:not(.user-message) { background-color: #ffffff; color: #333333; } .bottom-bar { position: fixed; bottom: 0; width: 100%; display: flex; justify-content: space-between; align-items: center; padding: 10px 0px; z-index: 10; box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1); } .bottom-input { display: flex; flex: 1; position: relative; } .record-btn { width: 40px; height: 40px; border-radius: 50%; display: flex; justify-content: center; align-items: center; } .record-text { font-size: 14px; } .input-box { flex: 1; margin: 0 10px; height: 40px; border: none; background-color: #f5f5f5; border-radius: 20px; padding: 0 15px; font-size: 14px; outline: none; } .send-btn { width: 40px; height: 40px; border-radius: 50%; background-color: #4CAF50; color: white; display: flex; justify-content: center; align-items: center; position: absolute; right: 10px; } .send-text { font-size: 18px; } .recording-indicator { position: fixed; top: 20px; right: 20px; z-index: 100; } .recording-circle { width: 10px; height: 10px; background-color: #ff0000; border-radius: 50%; animation: blink 1s infinite; } @keyframes blink { 0% { opacity: 1; } 50% { opacity: 0.3; } 100% { opacity: 1; } } /deep/ .uni-video-bar { display: none !important; } </style>在这个页面使用
07-08
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值