Browse Source

feat: 歌单添加界面
1. 新增歌单添加界面
2. 增加tooltip指令

kindring 2 months ago
parent
commit
beb9b985b3

+ 127 - 0
src/components/music/dialog/addPlayList.vue

@@ -0,0 +1,127 @@
+<script setup lang="ts">
+import {defineComponent, onMounted, PropType, ref} from "vue";
+import {PlayList} from "@/types/musicType.ts";
+import KuiInput from "@/components/public/kui/kui-input.vue";
+import KuiCheckbox from "@/components/public/kui/kui-checkbox.vue";
+
+defineComponent({
+  name: "addPlayList"
+})
+
+const props = defineProps({
+  playlist: {
+    type: Object as PropType<PlayList>,
+    default: (): PlayList => {
+      return {
+        id: -1,
+        name: '',
+        icon: '',
+        cover: '',
+        description: '',
+        playCount: 0,
+        trackCount: 0,
+        createTime: 0,
+        lastPlayTime: 0,
+        isTagSearch: false,
+        isSync: false,
+        isPublic: false,
+        isLike: false
+      }
+    }
+  }
+})
+
+const emits = defineEmits<{
+  (e: 'close' ): void,
+  (e: 'submit'): void
+}>()
+
+const isEdit = ref(false);
+// 歌单名称
+const playlistName = ref('');
+// 歌单描述
+const description = ref('');
+// 歌单封面
+const cover = ref('');
+const isLike = ref(false);
+const isPublic = ref(false);
+
+
+onMounted(()=>{
+  if (props.playlist && props.playlist.id > -1)
+  {
+    console.log(props.playlist);
+    isEdit.value = true;
+    playlistName.value = props.playlist.name;
+    description.value = props.playlist.description;
+    cover.value = props.playlist.cover;
+    console.log(cover.value);
+    isLike.value = !!props.playlist.isLike;
+    isPublic.value = !!props.playlist.isPublic;
+  }else {
+    isEdit.value = false;
+  }
+})
+
+
+function closeDialog() {
+  emits('close');
+}
+
+async function submitHandle() {
+  console.log('submit');
+}
+
+</script>
+
+<template>
+  <div class="dialog-content form-dialog">
+    <div class="dialog-title">
+      {{ isEdit? '编辑歌单' : '创建歌单' }}
+      <div
+          class="close-btn"
+          @click="closeDialog()">
+        X
+      </div>
+
+    </div>
+    <div class="dialog-show">
+      <div class="form-row" >
+        <kui-input label="歌单名称"
+                   placeholder="歌单的名称"
+                   v-model:value="playlistName"
+        />
+      </div>
+
+      <div class="form-row mt-4" >
+        <kui-input
+            label="歌单描述"
+            placeholder="歌单的描述"
+            v-model:value="description"
+        />
+      </div>
+
+      <div class="form-row mt-4">
+        <kui-checkbox id="isPublicPlaylist" v-model:value="isPublic" >
+          是否公开
+        </kui-checkbox>
+      </div>
+
+
+    </div>
+    <div class="dialog-footer">
+      <div class="btn cancel-btn mr-2" @click="closeDialog()">取消</div>
+      <div class="btn primary-btn" @click="submitHandle">确定</div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.form-row{
+  width: 90%;
+  height: 40px;
+  display: flex;
+  align-items: center;
+  margin: 0 auto;
+}
+</style>

+ 0 - 2
src/components/music/dialog/addScan.vue

@@ -66,9 +66,7 @@ async function selectPathHandle() {
   }
 }
 
-async function addScanDirHandle() {
 
-}
 async function submitHandle() {
 
   let param: MusicScanSetting = {

+ 32 - 2
src/components/music/musicIndex.vue

@@ -9,12 +9,14 @@ import {fetchPlayList, fetchScanConfig, musicAppStart} from "@/apis/musicControl
 import {ErrorCode} from "@/types/apiTypes.ts";
 import {Tab, TabGroup, TabList, TabPanel, TabPanels} from "@headlessui/vue";
 import ScanListInfo from "@/components/music/common/scanListInfo.vue";
+import {KuiDialogCmd} from "@/components/public/kui-dialog-cmd.ts";
+import addPlayList from "@/components/music/dialog/addPlayList.vue";
 
 defineComponent({
   name: "musicIndex"
 })
 
-defineProps({
+const props = defineProps({
   windowId: {
     type: String,
     default: ''
@@ -94,7 +96,8 @@ async function fetchScanSetting()
 }
 
 async function changeTab(index: number) {
-  if (site_view_key.value === site_views[index])
+
+  if (site_view_key.value === site_views[index] )
   {
     return;
   }
@@ -122,6 +125,28 @@ function changeScanList(index: number)
   selectScanIndex.value = index;
   musicViewShow.value = showScanList;
 }
+
+
+const kuiDialog = new KuiDialogCmd({
+  showContent: addPlayList,
+  mountTarget: props.windowId,
+  className: 'dialog',
+  on: {
+  },
+  onClose: closeDialogHandle
+});
+
+function closeDialogHandle()
+{
+  loadPlayList();
+  return true;
+}
+
+function addBtnClickHandle()
+{
+  message.info("add btn click");
+  kuiDialog.show()
+}
 </script>
 
 <template>
@@ -183,6 +208,11 @@ function changeScanList(index: number)
 
 <!--        设置-->
         <div class="setting-group">
+          <icon-svg class="icon mr-2"
+                    v-if="musicViewShow === showPlayList"
+                    @click.stop.capture="addBtnClickHandle"
+                    v-tooltip.top="'添加歌单'"
+                    icon-name="add"/>
           <icon-svg class="icon" @click.stop.capture="showMusicSetting" icon-name="setting"/>
         </div>
       </div>

+ 129 - 0
src/components/public/tooltip.vue

@@ -0,0 +1,129 @@
+<script>
+import {ref, computed} from 'vue'
+export default {
+  setup(){
+
+    // 显示弹框
+    const tooltipShow = ref(false);
+
+    // 提示内容
+    const text = ref()
+
+    // 方向
+    const placements = ref('left')
+
+    // 显示
+    function showTip(){
+      tooltipShow.value = true
+    }
+    function hiddenTip(){
+      tooltipShow.value = false
+    }
+
+    // 位置
+    const tooltipPostiton = ref({
+      x: 0,
+      y: 0
+    })
+    const tooltipStyle = computed(()=>{
+      return {
+        transform: `translate3d(${tooltipPostiton.value.x}px,${tooltipPostiton.value.y}px,0)`
+      }
+    })
+
+    return {
+      tooltipShow,
+      showTip,
+      hiddenTip,
+      tooltipPostiton,
+      tooltipStyle,
+      text,
+      placements,
+    }
+  }
+}
+</script>
+
+<template>
+  <!-- 指示 -->
+  <transition name="tooltip">
+    <div class="zc-tooltip" v-show="tooltipShow" :style="tooltipStyle"
+    >
+      <span class="zc-tooltip-text" v-html="text"></span>
+      <div class="zc-tooltip-arrow" :class="[{'left':placements=='left'},
+                                            {'bottom':placements=='bottom'},
+                                            {'right':placements=='right'},
+                                            {'top':placements=='top'}]"></div>
+    </div>
+  </transition>
+</template>
+
+
+<style  scoped>
+.zc-tooltip {
+  padding: 10px;
+  font-size: 12px;
+  line-height: 1.2;
+  min-width: 10px;
+  word-wrap: break-word;
+  position: fixed;
+  left: 0;
+  top: 0;
+  background: #303133;
+  color: #fff;
+  z-index: 1000;
+  display: inline-block;
+  border-radius: 8px;
+  font-weight: 500;
+  pointer-events: none;
+}
+
+.zc-tooltip-arrow {
+  position: absolute;
+  width: 0;
+  height: 0;
+  border-width: 8px;
+  border-style: solid;
+}
+
+.zc-tooltip-arrow.left {
+  border-color: transparent transparent transparent #303133;
+  right: -15px;
+  top: 50%;
+  transform: translate3d(0, -50%, 0);
+}
+
+.zc-tooltip-arrow.bottom {
+  top: -15px;
+  border-color: transparent transparent #303133 transparent;
+  left: 50%;
+  transform: translate3d(-50%, 0, 0);
+}
+
+.zc-tooltip-arrow.right {
+  left: -15px;
+  top: 50%;
+  transform: translate3d(0, -50%, 0);
+  border-color: transparent #303133 transparent transparent;
+}
+
+.zc-tooltip-arrow.top {
+  bottom: -15px;
+  border-color: #303133 transparent transparent transparent;
+  left: 50%;
+  transform: translate3d(-50%, 0, 0);
+}
+
+/* 动画 */
+.tooltip-enter-from,
+.tooltip-leave-to {
+  opacity: 0;
+  transition: opacity .3s ease;
+}
+
+.tooltip-leave-from,
+.tooltip-enter-to {
+  transition: opacity .1s ease;
+}
+
+</style>

+ 111 - 0
src/components/public/tooltipe_directive.ts

@@ -0,0 +1,111 @@
+// 引入组件
+import {nextTick, createApp, App} from "vue";
+import tooltip from './tooltip.vue'
+import {randomId} from "@/util/random.ts";
+
+const ids: string[] = []
+
+function tokenId ()
+{
+    let id = randomId()
+    let i = 0;
+    while (ids.includes(id)) {
+        id = randomId()
+        i++ ;
+        if (i >= 10) {
+            return id + ids.length
+        }
+    }
+    return id
+}
+
+// 清除监听
+function clearEvent(el: any) {
+    if (el._tipHandler) {
+        el.removeEventListener('mouseenter', el._tipHandler)
+    }
+    if (el._tipMouseleaveHandler) {
+        el.removeEventListener('mouseleave', el._tipMouseleaveHandler)
+    }
+    delete el._tipHandler
+    delete el._tipMouseleaveHandler
+    delete el._tipOptions
+    delete el._tipInstance
+}
+
+// 位置定位
+function calculationLocation(el: tooltip, target: HTMLElement, placements: string) {
+    if (!el || !target) return;
+    el.tooltipPostiton.y = 0;
+    el.tooltipPostiton.x = 0;
+    let el_dom = el.$el.nextElementSibling.getBoundingClientRect()
+    let target_dom = target.getBoundingClientRect()
+
+    if (placements === "left") {
+        el.tooltipPostiton.x = target_dom.x - el_dom.width - 10
+        el.tooltipPostiton.y = target_dom.y - el_dom.height / 2 + target_dom.height / 2
+    } else if (placements === "bottom") {
+        el.tooltipPostiton.x = target_dom.x + target_dom.width / 2 - el_dom.width / 2
+        el.tooltipPostiton.y = target_dom.y + el_dom.height + 10
+    } else if (placements === "right") {
+        el.tooltipPostiton.x = target_dom.x + target_dom.width + 10
+        el.tooltipPostiton.y = target_dom.y - el_dom.height / 2 + target_dom.height / 2
+    } else if (placements === "top") {
+        el.tooltipPostiton.x = target_dom.x + target_dom.width / 2 - el_dom.width / 2
+        el.tooltipPostiton.y = target_dom.y - el_dom.height - 10
+    }
+}
+
+// 方向
+const allPlacements = ['left', 'bottom', 'right', 'top']
+
+export default {
+    install(app: App) {
+        app.directive('tooltip', {
+            mounted(el, binding) {
+                clearEvent(el)
+                // console.log(binding.value)
+                el._tipOptions = binding.value
+                el._tipHandler = () => {
+                    const limitPlacementQueue = allPlacements.filter(placement => binding.modifiers[placement])
+                    const placements = limitPlacementQueue.length ? limitPlacementQueue : allPlacements
+                    if (!el._tipInstance) {
+                        el._synopsis = createApp(tooltip)
+                        el._root = document.createElement('div')
+                        document.body.appendChild(el._root)
+                        el._root.id = `tooltip_${tokenId()}`
+                        el._tipInstance = el._synopsis.mount(el._root)
+                    }
+                    el._tipInstance.placements = placements[0]
+                    el._tipInstance.showTip()
+                    el._tipInstance.text = el._tipOptions
+                    nextTick(() => {
+                        calculationLocation(el._tipInstance, el, placements[0])
+                    })
+                    el._scrollHandler = () => {
+                        if (el._tipInstance.tooltipShow)
+                            calculationLocation(el._tipInstance, el, placements[0])
+                    }
+                    window.addEventListener('scroll', el._scrollHandler)
+                }
+                el._tipMouseleaveHandler = () => {
+                    if (el._tipInstance) {
+                        el._tipInstance.hiddenTip()
+                    }
+                }
+                el.addEventListener('mouseenter', el._tipHandler)
+                el.addEventListener('mouseleave', el._tipMouseleaveHandler)
+            },
+            updated(el, binding) {
+                el._tipOptions = binding.value
+            },
+            unmounted(el) {
+                if (el._tipInstance) {
+                    el._synopsis.unmount()
+                    document.body.removeChild(el._root)
+                }
+                window.removeEventListener('scroll', el._scrollHandler)
+            }
+        })
+    }
+}

+ 4 - 1
src/main.ts

@@ -5,6 +5,7 @@ import {windowInit} from "./util/pageHandle.ts";
 import {bindIconSvg} from "@/components/public/icon/iconSvg.ts";
 import magnetInfos from "@/components/magnets/magnetInfo.ts";
 import TimeMagnet from "@/components/magnets/timeMagnet.vue";
+import tooltipe_directive from "@/components/public/tooltipe_directive.ts";
 
 import kuiMessage from "@/components/public/kui/message"
 
@@ -15,6 +16,8 @@ windowInit(app, "main");
 bindIconSvg(app);
 
 kuiMessage.install(app);
-
+app.use(tooltipe_directive);
 app.mount('#app');
 app.component(magnetInfos.timeMagnetInfo.type, TimeMagnet);
+
+