| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991 | <template>  <div>    <el-form ref="form" :model="form" label-width="150px">      <el-form-item        prop="sysMusicScore.name"        label="曲名"        :rules="[{ required: true, message: '请输入曲名' }]"      >        <el-input placeholder="请输入曲名" v-model="form.sysMusicScore.name" />      </el-form-item>      <el-form-item        prop="sysMusicScore.musicScoreCategoriesId"        label="分类"        :rules="[{ required: true, message: '请选择分类' }]"      >        <el-cascader          v-model="form.sysMusicScore.musicScoreCategoriesId"          style="width: 100%"          :options="tree"          placeholder="请选择分类"          :props="treeProps"        ></el-cascader>        <!-- <el-select style="width: 100%!important;" v-model="form.sysMusicScore.musicScoreCategoriesId" placeholder="请选择声部">          <el-option            v-for="item in selects.subjects"            :value="item.id"            :label="item.name"            :key="item.id"          ></el-option>        </el-select> -->      </el-form-item>      <!-- 是否收费  免费  收费 -->      <el-form-item        prop="rankIdType"        label="是否收费"        :rules="[{ required: true, message: '请选择是否收费' }]"      >        <el-select          style="width: 100% !important"          v-model="form.rankIdType"          placeholder="请选择是否收费"          @change="rankChange"        >          <!-- <el-option            :value="item.id"            :label="item.name"            v-for="item in memberRankList"            :key="item.id"          ></el-option> -->          <el-option :value="0" label="免费"></el-option>          <el-option :value="1" label="收费"></el-option>        </el-select>      </el-form-item>      <el-form-item        prop="sysMusicScore.isOpenMetronome"        label="节拍器"        :rules="[{ required: true, message: '请选择节拍器' }]"      >        <template slot="label">          <span>            节拍器            <el-tooltip placement="top" popper-class="mTooltip">              <div slot="content">是否播放系统自带节拍器</div>              <i                class="el-icon-question"                style="font-size: 18px; color: #f56c6c"              ></i>            </el-tooltip>          </span>        </template>        <el-select          style="width: 100% !important"          v-model="form.sysMusicScore.isOpenMetronome"          placeholder="请选择节拍器"        >          <!-- <el-option            :value="item.id"            :label="item.name"            v-for="item in memberRankList"            :key="item.id"          ></el-option> -->          <el-option :value="0" label="不播放"></el-option>          <el-option :value="1" label="播放"></el-option>        </el-select>      </el-form-item>      <el-form-item label="重复节拍时长">        <template slot="label">          <span>            重复节拍时长            <el-tooltip placement="top" popper-class="mTooltip">              <div slot="content">2/4拍类似的节拍器是否重复时长</div>              <i                class="el-icon-question"                style="font-size: 18px; color: #f56c6c"              ></i>            </el-tooltip>          </span>        </template>        <el-select          style="width: 100% !important"          v-model="form.repeatedBeats"          placeholder="请选择是否重复节拍器时长"        >          <el-option :value="0" label="不重复"></el-option>          <el-option :value="1" label="重复"></el-option>        </el-select>      </el-form-item>      <!-- <el-form-item        prop="sysMusicScore.clientType"        label="客户端类型"        :rules="[{ required: true, message: '请选择客户端类型' }]"      >        <el-select          style="width: 100% !important"          v-model="form.sysMusicScore.clientType"          placeholder="请选择客户端类型"        >          <el-option value="NETWORK_ROOM" label="网络教室"></el-option>          <el-option value="SMART_PRACTICE" label="云教练"></el-option>        </el-select>      </el-form-item> -->      <!-- <el-form-item        prop="sysMusicScore.renderFrom"        label="渲染模式"        :rules="[{ required: true, message: '请选择渲染模式' }]"      >        <el-select          style="width: 100% !important"          v-model="form.sysMusicScore.renderFrom"          placeholder="请选择渲染模式"        >          <el-option value="H5" label="H5"></el-option>          <el-option value="APP" label="原生"></el-option>        </el-select>      </el-form-item> -->      <el-form-item        prop="sysMusicScore.playMode"        label="播放模式"        :rules="[{ required: true, message: '请选择播放模式' }]"      >        <el-select          style="width: 100% !important"          v-model="form.sysMusicScore.playMode"          placeholder="请选择播放模式"        >          <el-option value="MP3" label="MP3播放"></el-option>          <el-option value="XML" label="XML播放"></el-option>        </el-select>      </el-form-item>      <el-form-item        prop="sysMusicScore.enableEvaluation"        label="支持评测"        :rules="[{ required: true, message: '请选择支持评测' }]"      >        <el-radio-group v-model="form.sysMusicScore.enableEvaluation">          <el-radio :label="1">是</el-radio>          <el-radio :label="0">否</el-radio>        </el-radio-group>      </el-form-item>      <el-form-item :prop="`sysMusicScore.subjectId`" label="声部">        <!-- :rules="[{required: true, message: '请选择声部'}]" -->        <el-select          style="width: 100% !important"          v-model="form.sysMusicScore.subjectId"          clearable          placeholder="请选择声部"        >          <el-option            v-for="item in selects.subjects"            :value="item.id"            :label="item.name"            :key="item.id"            :disabled="hasSubjectId(item.id)"          ></el-option>        </el-select>      </el-form-item>      <el-form-item        :prop="`sysMusicScore.isShowFingering`"        label="指法展示"        :rules="[{ required: true, message: '请选择是否展示指法' }]"      >        <el-select          style="width: 100% !important"          v-model="form.sysMusicScore.isShowFingering"          placeholder="请选择是否展示指法"        >          <el-option :value="true" label="是"></el-option>          <el-option :value="false" label="否"></el-option>        </el-select>      </el-form-item>      <el-form-item        prop="sysMusicScore.order"        label="排序"        :rules="[          { required: true, message: '请输入排序' },          {            pattern: /^([1-9]\d*|[0]{1,1})$/,            message: '请输入正确的排序',            trigger: 'blur',          },        ]"      >        <el-input placeholder="请输入排序" v-model="form.sysMusicScore.order" />      </el-form-item>      <el-form-item        label="MusicXML"        :prop="`sysMusicScore.xmlUrl`"        :rules="[{ required: true, message: '请选择MusicXML文件' }]"      >        <singe-file-upload          tips="仅支持上传 xml 格式文件"          accept=".xml"          v-model="form.sysMusicScore.xmlUrl"          @inputFile="inputFile"          bucket_name="cloud-coach"        />      </el-form-item>      <el-form-item        prop="sysMusicScore.speed"        label="速度"        :rules="[{ required: true, message: '请输入速度' }]"      >        <el-input          placeholder="请输入速度"          v-model="form.sysMusicScore.speed"        />      </el-form-item>      <el-form-item        v-if="form.sysMusicScore.isOpenMetronome === 1"        label="伴奏(不含节拍器)"        prop="sysMusicScore.url"      >        <singe-file-upload          tips="仅支持上传 mp3/aac 格式音频文件"          accept=".mp3, .aac"          v-model="form.sysMusicScore.url"          bucket_name="cloud-coach"        />      </el-form-item>      <el-form-item        v-else        label="伴奏(含节拍器)"        prop="sysMusicScore.metronomeUrl"      >        <singe-file-upload          tips="仅支持上传 mp3/aac 格式音频文件"          accept=".mp3, .aac"          v-model="form.sysMusicScore.metronomeUrl"          bucket_name="cloud-coach"        />      </el-form-item>      <el-form-item label="MIDI" prop="sysMusicScore.midiUrl">        <singe-file-upload          tips="仅支持上传 mid 格式音频文件"          accept=".mid"          v-model="form.sysMusicScore.midiUrl"          bucket_name="cloud-coach"        />      </el-form-item>      <div v-if="gradual && gradual.length">        <el-alert :closable="false" style="margin-bottom: 20px"          >识别到共{{            gradual.length          }}处渐变速度,请输入Dorico对应小节时间信息</el-alert        >        <div v-for="(item,index) in gradual" :key="index">          <el-form-item            :label="item[0].measureIndex + 2 + ' 小节'"            :rules="[              { required: true, message: '请输入合奏曲目时间' },              {                pattern: /^((\d{2}):?){2,3}$/,                message: '请输入正确的曲目时间',                trigger: 'blur',              },            ]"            :prop="`graduals.${item[0].measureIndex}`"          >            <el-input              placeholder="00:00:00"              v-model="form.graduals[item[0].measureIndex]"            ></el-input>          </el-form-item>          <el-form-item            :label="item[1].measureIndex + 2 + ' 小节'"            :rules="[              { required: true, message: '请输入合奏曲目时间' },              {                pattern: /^((\d{2}):?){2,3}$/,                message: '请输入正确的曲目时间',                trigger: 'blur',              },            ]"            :prop="`graduals.${item[1].measureIndex}`"          >            <el-input              placeholder="00:00:00"              v-model="form.graduals[item[1].measureIndex]"            ></el-input>          </el-form-item>        </div>      </div>      <div        class="files"        v-for="(song, index) in form.sysMusicScoreAccompaniments"        :key="index"      >        <el-row>          <!-- <el-col :span="12">          </el-col> -->          <!-- <el-col :span="12">          </el-col> -->          <el-col :span="12" v-if="partListNames.length > 1">            <el-form-item              label="所属轨道"              :prop="`sysMusicScoreAccompaniments.${index}.track`"              :rules="[{ required: true, message: '请选择所属轨道' }]"            >              <el-select                style="width: 100% !important"                v-model="song.track"                clearable                placeholder="请选择轨道"              >                <el-option                  v-for="item in partListNames"                  :value="item"                  :label="item"                  :key="item"                  :disabled="hasPartName(item)"                ></el-option>              </el-select>            </el-form-item>          </el-col>          <el-col :span="12" v-if="form.sysMusicScore.isOpenMetronome === 1">            <el-form-item              label="原音(不含节拍器)"              :prop="`sysMusicScoreAccompaniments.${index}.mp3Url`"              :rules="[                {                  required: form.sysMusicScore.isOpenMetronome                    ? form.sysMusicScore.playMode === 'MP3'                    : false,                  message: '请上传原音(不含节拍器)',                },              ]"            >              <singe-file-upload                tips="仅支持上传 mp3/aac 格式音频文件"                accept=".mp3, .aac"                v-model="song.mp3Url"                bucket_name="cloud-coach"              />            </el-form-item>          </el-col>          <el-col :span="12" v-else>            <el-form-item              label="原音(含节拍器)"              :prop="`sysMusicScoreAccompaniments.${index}.metronomeMp3Url`"              :rules="[                {                  required: form.sysMusicScore.isOpenMetronome                    ? false                    : form.sysMusicScore.playMode === 'MP3',                  message: '原音(含节拍器)',                },              ]"            >              <singe-file-upload                tips="仅支持上传 mp3/aac 格式音频文件"                accept=".mp3, .aac"                v-model="song.metronomeMp3Url"                bucket_name="cloud-coach"              />            </el-form-item>          </el-col>        </el-row>        <el-row>          <el-col :span="24">            <el-form-item              :prop="`sysMusicScoreAccompaniments.${index}.memo`"              label="描述"            >              <el-input                type="textarea"                :rows="2"                placeholder="请输入描述"                v-model="song.memo"              />            </el-form-item>          </el-col>          <!-- <el-col :span="12">          </el-col> -->        </el-row>        <el-button          class="file-remove"          type="text"          @click="removeSys(index)"          :disabled="form.sysMusicScoreAccompaniments.length == 1"          >删除</el-button        >      </div>      <el-button        @click="createSys"        type="info"        style="width: 100%; margin-bottom: 20px"        plain        >添加原音</el-button      >      <div class="btns">        <el-button type="primary" @click="submit">提交</el-button>        <el-button @click="$listeners.close">取消</el-button>      </div>    </el-form>  </div></template><script>import axios from "axios";import { Add, Update, queryPageSysExam, queryTree } from "../api";import { getAllmemberRank } from "@/views/resetTeaming/api";const initailExtConfigJson = {  repeatedBeats: 0,};export default {  props: ["detail", "type"],  data() {    return {      gradual: null,      xmlFirstSpeed: "",      partListNames: [],      tree: [],      extConfigJson: {},      memberRankList: [], // 会员列表      form: {        graduals: {},        rankIdType: 0, // 收费会员类型 默认免费        repeatedBeats: 0, // 重复节拍        sysMusicScore: {          isOpenMetronome: 0, // 是否开启节拍器 默认关闭          name: "",          rankIds: "", // 收费会员编号          url: "",          metronomeUrl: "",          midiUrl: "",          order: "",          musicScoreCategoriesId: [],          // 兼容之前数据,默认选择云教练          clientType: "SMART_PRACTICE",          renderFrom: "",          playMode: "MP3",          enableEvaluation: 1,          extConfigJson: "{}",          subjectId:null,          speed:'',          xmlUrl:null,          isShowFingering:null,        },        sysMusicScoreAccompaniments: [          {            subjectId: "",            speed: "",            mp3Url: "",            xmlUrl: "",            isShowFingering: true,            mome: "",            track: "",          },        ],        delExamSongAccompanimentIds: [],      },      treeProps: {        value: "id",        label: "name",        children: "sysMusicScoreCategoriesList",      },    };  },  async mounted() {    this.$store.dispatch("setSubjects");    await this.FetchTree();    await this.memberRank();    if (this.detail) {      try {        this.extConfigJson = JSON.parse(this.detail.extConfigJson);      } catch (error) {        this.extConfigJson = { ...initailExtConfigJson };      }      this.form.repeatedBeats = this.extConfigJson.repeatedBeats;      this.form.graduals = this.extConfigJson.gradualTimes || {};      this.$set(this.form, "sysMusicScore", {        isOpenMetronome: Number(this.detail.isOpenMetronome),        name: this.detail.name,        url: this.detail.url,        midiUrl: this.detail.midiUrl,        rankIds: this.detail.rankIds,        order: this.detail.order,        clientType: this.detail.clientType,        enableEvaluation: +this.detail.enableEvaluation,        metronomeUrl: this.detail.metronomeUrl,        renderFrom: this.detail.renderFrom,        playMode: this.detail.playMode,        musicScoreCategoriesId: this.detail.categoriesId          ? this.formatParentId(this.detail.categoriesId, this.tree)          : [],      });      if (this.detail.rankIds) {        this.form.rankIdType = 1;      } else {        this.form.rankIdType = 0;      }      const xmlres = await axios(this.detail.xmlUrl);      this.gradual = getGradualLengthByXml(xmlres.data).filter(        (item) => item.length === 2      );      this.partListNames = this.getPartListNames(xmlres.data);      this.FeatchDetailList();    } else {      // 新增条件下默认设置为收费      this.rankChange(1);      this.form.rankIdType = 1;    }  },  methods: {    getPartListNames(xml) {      if (!xml) return [];      const xmlParse = new DOMParser().parseFromString(xml, "text/xml");      const partList =        xmlParse          .getElementsByTagName("part-list")?.[0]          ?.getElementsByTagName("score-part") || [];      const partListNames = Array.from(partList).map(        (item) => item.getElementsByTagName("part-name")?.[0].textContent || ""      );      this.xmlFirstSpeed =        xmlParse.getElementsByTagName("per-minute")?.[0]?.textContent || "";      // this.form.sysMusicScore.speed = this.xmlFirstSpeed;      this.$set(this.form.sysMusicScore,'speed',this.xmlFirstSpeed)      return partListNames.filter(        (text) => text.toLocaleUpperCase() !== "COMMON"      );    },    inputFile(file) {      const xmlRead = new FileReader();      xmlRead.onload = (res) => {        this.partListNames = this.getPartListNames(res.target.result);        this.gradual = getGradualLengthByXml(res.target.result).filter(          (item) => item.length === 2        );        for (let j = 0; j < this.form.sysMusicScoreAccompaniments.length; j++) {          this.form.sysMusicScoreAccompaniments[j].track =            this.partListNames[j];          if (!this.form.sysMusicScoreAccompaniments[j].speed) {            this.form.sysMusicScoreAccompaniments[j].speed = this.xmlFirstSpeed;          }          this.$set(            this.form,            "sysMusicScoreAccompaniments",            this.form.sysMusicScoreAccompaniments          );        }        for (          let index = this.form.sysMusicScoreAccompaniments.length;          index < this.partListNames.length;          index++        ) {          const part = this.partListNames[index];          const sysData = {            ...this.form.sysMusicScoreAccompaniments[0],            metronomeMp3Url: "",            mp3Url: "",            track: part,          };          if (!sysData.speed) {            sysData.speed = this.xmlFirstSpeed;          }          this.createSys(sysData);        }      };      xmlRead.readAsText(file.raw);    },    rankChange(value) {      if (value) {        let tempIds = [];        this.memberRankList.forEach((item) => {          tempIds.push(item.id);        });        this.form.sysMusicScore.rankIds = tempIds.join(",");      } else {        // 会员购买重置        this.form.sysMusicScore.rankIds = "";      }    },    async memberRank() {      try {        const res = await getAllmemberRank({ isDefault: 0 });        this.memberRankList = res.data || [];      } catch (e) {        console.log(e);      }    },    formatParentId(id, list, ids = []) {      for (const item of list) {        if (item.sysMusicScoreCategoriesList) {          const cIds = this.formatParentId(            id,            item.sysMusicScoreCategoriesList,            [...ids, item.id]          );          if (cIds.includes(id)) {            return cIds;          }        }        if (item.id === id) {          return [...ids, id];        }      }      return ids;    },    async FetchTree() {      try {        const res = await queryTree();        this.tree = res.data;        this.formatTree(this.tree);      } catch (error) {}    },    formatTree(data) {      for (let i of data) {        if (          i.sysMusicScoreCategoriesList &&          i.sysMusicScoreCategoriesList.length > 0        ) {          this.formatTree(i.sysMusicScoreCategoriesList, i);        } else {          i.sysMusicScoreCategoriesList = null;        }      }    },    async FeatchDetailList() {      try {        const res = await queryPageSysExam({          sysMusicScoreId: this.detail.id,        });        const result = res.data || [];        result.forEach((item) => {          if (!item.subjectId) {            item.subjectId = null;          }        });      if(result.length >0){        console.log(result[0].speed)        this.$set(this.form.sysMusicScore, "subjectId", result[0].subjectId);        this.$set(this.form.sysMusicScore, "speed", result[0].speed);        this.$set(this.form.sysMusicScore, "xmlUrl", result[0].xmlUrl);        this.$set(this.form.sysMusicScore, "isShowFingering", result[0].isShowFingering);      }        this.$set(this.form, "sysMusicScoreAccompaniments", result);      } catch (error) {}    },    createSys(initData) {      this.form.sysMusicScoreAccompaniments.push(        Object.assign(          {            subjectId: "",            speed: "",            mp3Url: "",            xmlUrl: "",            track: "",          },          initData || {}        )      );    },    async removeSys(index) {      try {        await this.$confirm("是否确认删除此原音?", "提示", {          type: "warning",        });        if (this.form.sysMusicScoreAccompaniments[index]) {          this.form.delExamSongAccompanimentIds.push(            this.form.sysMusicScoreAccompaniments[index].id          );        }        this.form.sysMusicScoreAccompaniments.splice(index, 1);      } catch (error) {}    },    hasPartName(name) {      const names = [];      for (const item of this.form.sysMusicScoreAccompaniments) {        names.push(item.track);      }      return names.includes(name);    },    hasSubjectId(id) {      const ids = [];      for (const item of this.form.sysMusicScoreAccompaniments) {        ids.push(item.subjectId);      }      return ids.includes(id);    },    async submit() {      this.$refs.form.validate(async (valid) => {        if (valid) {          // 提交前平铺 速度,声部,XML,isShowFingering          this.form.sysMusicScoreAccompaniments.forEach(item=>{            item.speed = this.form.sysMusicScore.speed            item.subjectId = this.form.sysMusicScore.subjectId            item.spexmlUrled =  this.form.sysMusicScore.spexmlUrled            item.isShowFingering =  this.form.sysMusicScore.isShowFingering          })          if (!this.detail) {            await Add({              ...this.form,              sysMusicScore: {                ...this.form.sysMusicScore,                extConfigJson: JSON.stringify({                  repeatedBeats: this.form.repeatedBeats,                  gradualTimes: this.form.graduals,                }),                type: "COMMON",                showFlag: 0,                musicScoreCategoriesId: (                  this.form.sysMusicScore.musicScoreCategoriesId || []                ).pop(),              },            });            this.$message.success("提交成功");          } else {            await Update({              ...this.form,              sysMusicScore: {                ...this.form.sysMusicScore,                extConfigJson: JSON.stringify({                  repeatedBeats: this.form.repeatedBeats,                  gradualTimes: this.form.graduals,                }),                type: "COMMON",                id: this.detail.id,                showFlag: this.detail.showFlag,                musicScoreCategoriesId: (                  this.form.sysMusicScore.musicScoreCategoriesId || []                ).pop(),              },            });            this.$message.success("修改成功");          }          this.$listeners.close();          this.$listeners.submited();        }      });    },  },};/** * 获取指定元素下一个Note元素 * @param ele 指定元素 * @param selectors 选择器 */const getNextNote = (ele, selectors) => {  let index = 0;  const parentEle = ele.closest(selectors);  let pointer = parentEle;  const measure = parentEle?.closest("measure");  let siblingNote = null;  // 查找到相邻的第一个note元素  while (!siblingNote && index < (measure?.childNodes.length || 50)) {    index++;    if (pointer?.nextElementSibling?.tagName === "note") {      siblingNote = pointer?.nextElementSibling;    }    pointer = pointer?.nextElementSibling;  }  return siblingNote;};export const onlyVisible = (xml, partIndex) => {  if (!xml) return "";  const xmlParse = new DOMParser().parseFromString(xml, "text/xml");  const partList =    xmlParse      .getElementsByTagName("part-list")?.[0]      ?.getElementsByTagName("score-part") || [];  // const partListNames = Array.from(partList).map(item => item.getElementsByTagName('part-name')?.[0].textContent || '')  const parts = xmlParse.getElementsByTagName("part");  // const firstTimeInfo = parts[0]?.getElementsByTagName('metronome')[0]?.parentElement?.parentElement?.cloneNode(true)  // const firstMeasures = [...Array.from(parts[0]?.getElementsByTagName('measure') || [])]  // const metronomes = [...Array.from(parts[0]?.getElementsByTagName('metronome') || [])]  // const words = [...Array.from(parts[0]?.getElementsByTagName('words') || [])]  // const codas = [...Array.from(parts[0]?.getElementsByTagName('coda') || [])]  // const rehearsals = [...Array.from(parts[0]?.getElementsByTagName('rehearsal') || [])]  const visiblePartInfo = partList[partIndex];  // console.log(visiblePartInfo, partIndex)  // state.partListNames = partListNames  if (visiblePartInfo) {    const id = visiblePartInfo.getAttribute("id");    Array.from(parts).forEach((part) => {      if (part && part.getAttribute("id") !== id) {        part.parentNode?.removeChild(part);        // 不等于第一行才添加避免重复添加      } else {        // words.forEach(word => {        //   const text = word.textContent || ''        //   if(isSpeedKeyword(text) && text) {        //     const wordContainer = word.parentElement?.parentElement?.parentElement        //     if (wordContainer && wordContainer.firstElementChild && wordContainer.firstElementChild !== word) {        //       const wordParent = word.parentElement?.parentElement        //       const fisrt = wordContainer.firstElementChild        //       wordContainer.insertBefore(wordParent, fisrt)        //     }        //   }        // })      }      // 最后一个小节的结束线元素不在最后 调整      if (part && part.getAttribute("id") === id) {        const barlines = part.getElementsByTagName("barline");        const lastParent = barlines[barlines.length - 1]?.parentElement;        if (lastParent?.lastElementChild?.tagName !== "barline") {          const children = lastParent?.children || [];          for (let el of children) {            if (el.tagName === "barline") {              // 将结束线元素放到最后              lastParent?.appendChild(el);              break;            }          }        }      }    });    Array.from(partList).forEach((part) => {      if (part && part.getAttribute("id") !== id) {        part.parentNode?.removeChild(part);      }    });    // 处理装饰音问题    const notes = xmlParse.getElementsByTagName("note");    const getNextvNoteDuration = (i) => {      let nextNote = notes[i + 1];      // 可能存在多个装饰音问题,取下一个非装饰音时值      for (let index = i; index < notes.length; index++) {        const note = notes[index];        if (!note.getElementsByTagName("grace")?.length) {          nextNote = note;          break;        }      }      const nextNoteDuration = nextNote?.getElementsByTagName("duration")[0];      return nextNoteDuration;    };    Array.from(notes).forEach((note, i) => {      const graces = note.getElementsByTagName("grace");      if (graces && graces.length) {        // if (i !== 0) {        note.appendChild(getNextvNoteDuration(i)?.cloneNode(true));        // }      }    });  }  // console.log(new XMLSerializer().serializeToString(xmlParse))  return new XMLSerializer().serializeToString(xmlParse);};const speedInfo = {  "rall.": 1.333333333,  "poco rit.": 1.333333333,  "rit.": 1.333333333,  "molto rit.": 1.333333333,  "molto rall": 1.333333333,  lentando: 1.333333333,  allargando: 1.333333333,  morendo: 1.333333333,  "accel.": 0.8,  calando: 2,  "poco accel.": 0.8,};/** * 按照xml进行减慢速度的计算 * @param xml 始终按照第一分谱进行减慢速度的计算 */export function getGradualLengthByXml(xml) {  const firstPartXml = onlyVisible(xml, 0);  const xmlParse = new DOMParser().parseFromString(firstPartXml, "text/xml");  const measures = Array.from(xmlParse.querySelectorAll("measure"));  const notes = Array.from(xmlParse.querySelectorAll("note"));  const words = Array.from(xmlParse.querySelectorAll("words"));  const metronomes = Array.from(xmlParse.querySelectorAll("metronome"));  const eles = [];  for (const ele of [...words, ...metronomes]) {    const note = getNextNote(ele, "direction");    // console.log(ele, note)    if (note) {      const measure = note?.closest("measure");      const measureNotes = Array.from(measure.querySelectorAll("note"));      const noteInMeasureIndex = Array.from(measure.childNodes)        .filter((item) => item.nodeName === "note")        .findIndex((item) => item === note);      let allDuration = 0;      let leftDuration = 0;      for (let i = 0; i < measureNotes.length; i++) {        const n = measureNotes[i];        const duration = +(n.querySelector("duration")?.textContent || "0");        allDuration += duration;        if (i < noteInMeasureIndex) {          leftDuration = allDuration;        }      }      eles.push({        ele,        index: notes.indexOf(note),        noteInMeasureIndex,        textContent: ele.textContent,        measureIndex: measures.indexOf(measure),        type: ele.tagName,        allDuration,        leftDuration,      });    }  }  const gradualNotes = [];  eles.sort((a, b) => a.index - b.index);  const keys = Object.keys(speedInfo).map((w) => w.toLocaleLowerCase());  for (const ele of eles) {    const textContent = ele.textContent?.toLocaleLowerCase().trim();    if (ele.type === "words" && keys.includes(textContent)) {      gradualNotes.push([        {          start: ele.index,          measureIndex: ele.measureIndex,          noteInMeasureIndex: ele.noteInMeasureIndex,          allDuration: ele.allDuration,          leftDuration: ele.leftDuration,          type: textContent,        },      ]);    }    if (      ele.type === "metronome" ||      (ele.type === "words" && textContent === "a tempo")    ) {      const indexOf = gradualNotes.findIndex((item) => item.length === 1);      if (indexOf > -1 && ele.index > gradualNotes[indexOf]?.[0].start) {        gradualNotes[indexOf][1] = {          start: ele.index,          measureIndex: ele.measureIndex,          noteInMeasureIndex: ele.noteInMeasureIndex,          allDuration: ele.allDuration,          leftDuration: ele.leftDuration,          type: textContent,        };      }    }  }  return gradualNotes;}</script><style lang="less" scoped>.btns {  text-align: right;}.files {  background-color: #f8f8f8;  padding: 20px 0;  padding-right: 20px;  margin-bottom: 20px;  border-radius: 5px;  position: relative;  .file-remove {    position: absolute;    right: 20px;    bottom: 10px;  }}</style>
 |