浏览代码

feat: 编辑扫描配置
1. 编辑扫描配置功能

kindring 3 月之前
父节点
当前提交
eea43ee2e2

+ 13 - 1
src/apis/musicControl.ts

@@ -25,8 +25,20 @@ export async function addScanDir(scanSetting: MusicScanSetting)
     return await promise;
 }
 
+export async function updateScanConfig(scanConfig: MusicScanSetting)
+{
+    let [_callId, promise] = api.sendQuery(Music_Actions.scan_music_update, scanConfig);
+    return await promise;
+}
+
 export async function fetchScanConfig() : Promise<ResponseData<MusicScanSetting[]>>
 {
     let [_callId, promise] = api.sendQuery(Music_Actions.scan_settings, {});
     return await promise;
-}
+}
+
+export async function deleteScanConfig(id: number) : Promise<ResponseData<boolean>>
+{
+    let [_callId, promise] = api.sendQuery(Music_Actions.scan_music_delete, id);
+    return await promise;
+}

+ 21 - 1
src/assets/base.css

@@ -202,7 +202,7 @@
 
 
 
-.dialog{
+.dialog, kui-dialog{
     width: 100%;
     height: 100%;
     position: absolute;
@@ -283,6 +283,8 @@
 }
 
 
+
+
 .site{
     width: 100%;
     height: 100%;
@@ -453,6 +455,24 @@
     border: 1px solid #bbb;
 }
 
+.circle-btn{
+    width: 40px;
+    height: 40px;
+    border-radius: 50%;
+    cursor: pointer;
+    background-color: #e74c3c;
+    color: #fff;
+    display: flex;
+    padding: 0px;
+    font-size: 1.2rem;
+    justify-content: center;
+    align-items: center;
+    transition: all 0.3s;
+}
+.circle-btn:hover{
+    background-color: #c0392b;
+    color: #fff;
+}
 
 .event-mask{
     position: absolute;

+ 7 - 3
src/common/db/db_music.ts

@@ -203,7 +203,7 @@ export async function initMusicData() : PromiseResult<boolean>
 
 
 // 根据扫描地址获取扫描配置
-export async function getScanConfigByPath(path: string) : PromiseResult<MusicScanSetting[]>
+export async function getScanConfigByPath(path: string, id: number[] = []) : PromiseResult<MusicScanSetting[]>
 {
     let db = loadDb(AppDbName.music_db)
     if(!db){
@@ -214,6 +214,8 @@ export async function getScanConfigByPath(path: string) : PromiseResult<MusicSca
         db.select('name', 'path', 'scanSubDir', 'isFileRepeat')
             .from(MusicTableName.music_scan_setting)
             .where('path', path)
+            // 排除id
+            .whereNotIn('id', id)
     )
     if (err) {
         err = err as Error;
@@ -230,9 +232,11 @@ export async function getScanConfig() : PromiseResult<MusicScanSetting[]>
         logger.error('数据库初始化失败')
         return [new Error('音乐数据库初始化失败'), null]
     }
+    // 将下面的 scanSubDir 转为 boolean 类型
+
     let [err, res] = await handle(
         db.select('id', 'name', 'path', 'scanSubDir', 'isFileRepeat')
-            .from(MusicTableName.music_scan_setting)
+            .from<MusicScanSetting>(MusicTableName.music_scan_setting).withSchema(MusicTableName.music_scan_setting)
     )
     if (err) {
         err = err as Error;
@@ -275,7 +279,7 @@ export async function updateScanConfig(scanConfig: MusicScanSetting) : PromiseRe
         return [new Error('音乐数据库初始化失败'), false]
     }
     let [err, _res] = await handle(
-        db.update(scanConfig).into(MusicTableName.music_scan_setting)
+        db.update(scanConfig).into(MusicTableName.music_scan_setting).where('id', scanConfig.id)
     )
     if (err) {
         err = err as Error;

+ 51 - 15
src/components/music/common/mSettingScan.vue

@@ -1,12 +1,12 @@
 <script setup lang="ts">
-import {defineComponent, onBeforeMount, Ref, ref} from "vue";
+import {defineComponent, nextTick, onBeforeMount, Reactive, reactive, Ref, ref} from "vue";
 import {MusicScanSetting} from "@/types/musicType.ts";
 import EmptyView from "@/components/public/emptyView.vue";
-import {KuiDialogCmd} from "@/components/public/kui-dialog-cmd.ts";
+import {KuiDialogCmd, showAlert} from "@/components/public/kui-dialog-cmd.ts";
 
 import addScanDialog from "@/components/music/dialog/addScan.vue"
 import message from "@/components/public/kui/message";
-import {fetchScanConfig} from "@/apis/musicControl.ts";
+import {deleteScanConfig, fetchScanConfig} from "@/apis/musicControl.ts";
 import {ErrorCode} from "@/types/apiTypes.ts";
 import IconSvg from "@/components/public/icon/iconSvg.vue";
 
@@ -41,7 +41,7 @@ const kuiDialog = new KuiDialogCmd({
 
 function closeScanDialogHandle()
 {
-  // fetchScanSetting();
+  fetchScanSetting();
   return true;
 }
 
@@ -62,7 +62,6 @@ async function fetchScanSetting()
   {
     scanSetting.value = responseData.data;
   }
-  console.log(responseData);
 }
 
 onBeforeMount(()=>{
@@ -72,7 +71,36 @@ onBeforeMount(()=>{
 
 function editScanHandle(item: MusicScanSetting)
 {
-  kuiDialog.show({scanSetting: item});
+  kuiDialog.show({scanSetting: {
+    id: item.id,
+    name: item.name,
+    path: item.path,
+    scanSubDir: !!item.scanSubDir,
+    isFileRepeat: !!item.isFileRepeat
+    }});
+}
+
+async function exe_deleteScan(id: number)
+{
+  let responseData = await deleteScanConfig(id);
+  if (responseData.code === ErrorCode.success)
+  {
+    message.success(`删除成功`);
+    await fetchScanSetting();
+  } else{
+    message.error(responseData.msg);
+  }
+}
+
+async function deleteScanHandle(id: number)
+{
+  showAlert({
+    title: '删除扫描配置',
+    content: `确定要删除该扫描配置? id:${id}`,
+    onOk: () => {
+      exe_deleteScan(id)
+    }
+  }, props.windowId);
 }
 
 </script>
@@ -101,9 +129,12 @@ function editScanHandle(item: MusicScanSetting)
             <span class="value"> {{ item.isFileRepeat ? '是' : '否' }}</span>
           </div>
         </div>
-        <div class="edit-btn" @click="editScanHandle(item)">
+        <div class="edit-btn circle-btn" @click="editScanHandle(item)">
           <icon-svg icon-name="edit"/>
         </div>
+        <div class="delete-btn circle-btn" @click="deleteScanHandle(item.id)">
+          <icon-svg icon-name="remove"/>
+        </div>
       </div>
     </div>
 
@@ -168,23 +199,28 @@ function editScanHandle(item: MusicScanSetting)
 }
 .edit-btn{
   position: absolute;
-  right: 5px;
+  right: 40px;
   top: 5px;
   width: 30px;
   height: 30px;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  cursor: pointer;
-  transition: all 0.2s ease-in-out;
-  border-radius: 50%;
-  box-shadow: 0 0 5px 2px white;
   font-size: 1.4rem;
 }
 .edit-btn:hover{
   background-color: var(--color-btn-bg-hover);
   color: var(--color-btn-text-hover);
 }
+.delete-btn{
+  position: absolute;
+  right: 5px;
+  top: 5px;
+  width: 30px;
+  height: 30px;
+  font-size: 1.4rem;
+}
+.delete-btn:hover{
+  background-color: var(--color-btn-bg-hover);
+  color: var(--color-btn-text-hover);
+}
 
 
 

+ 19 - 4
src/components/music/dialog/addScan.vue

@@ -2,9 +2,10 @@
 import {defineComponent, onMounted, PropType, ref} from "vue";
 import KuiInput from "@/components/public/kui/kui-input.vue";
 import message from "@/components/public/kui/message";
-import {addScanDir, selectScanDir} from "@/apis/musicControl.ts";
+import {addScanDir, selectScanDir, updateScanConfig} from "@/apis/musicControl.ts";
 import KuiCheckbox from "@/components/public/kui/kui-checkbox.vue";
 import {MusicScanSetting} from "@/types/musicType.ts";
+import {ResponseData} from "@/types/apiTypes.ts";
 
 defineComponent({
   name: "addScanDialog"
@@ -65,9 +66,13 @@ async function selectPathHandle() {
   }
 }
 
+async function addScanDirHandle() {
+
+}
 async function submitHandle() {
+
   let param: MusicScanSetting = {
-    id: 0,
+    id: props.scanSetting.id,
     name: name.value,
     path: dirPath.value,
     scanSubDir: scanSubDir.value,
@@ -78,13 +83,23 @@ async function submitHandle() {
     message.warning('请选择需要扫描的子目录');
     return;
   }
+
+  let responseData : ResponseData<boolean>
   if (!param.name)
   {
     param.name = param.path;
   }
-  let responseData = await addScanDir(param);
+  if ( isEdit.value )
+  {
+    param.id = props.scanSetting.id;
+    responseData = await updateScanConfig(param);
+  } else
+  {
+    responseData = await addScanDir(param);
+  }
+
   if (responseData.code === 0) {
-    message.success('添加扫描配置成功');
+    message.success(isEdit?'编辑扫描配置成功':'添加扫描配置成功');
     emits('close');
   } else {
     message.error(responseData.msg);

+ 117 - 0
src/components/public/alertModel.vue

@@ -0,0 +1,117 @@
+<script setup lang="ts">
+
+defineProps({
+  title: {
+    type: String,
+    default: ''
+  },
+  content: {
+    type: String,
+    default: ''
+  },
+  cancelText: {
+    type: String,
+    default: '取消'
+  },
+  okText: {
+    type: String,
+    default: '确定'
+  },
+  showCancel: {
+    type: Boolean,
+    default: true
+  }
+});
+
+
+const emits = defineEmits<{
+  (e: 'cancel' ): void,
+  (e: 'ok'): void
+}>()
+
+const closeHandle = () => {
+  emits('cancel')
+}
+const submitHandle = () => {
+  emits('ok')
+}
+</script>
+
+<template>
+  <div class="alert-dialog">
+    <div class="alert-title">
+      <span class="title-text">{{title}}</span>
+    </div>
+    <div class="alert-content">
+      {{content}}
+    </div>
+    <div class="alert-footer">
+      <div v-if="showCancel" class="btn" @click.prevent="closeHandle">{{ cancelText }}</div>
+      <div class="btn btn-primary" @click.prevent="submitHandle" >{{ okText }}</div>
+    </div>
+  </div>
+</template>
+
+<style scoped>
+.alert-dialog
+{
+  width: 270px;
+  height: auto;
+  position: absolute;
+  top: 50px;
+  left: 50%;
+  transform: translateX(-50%);
+  background-color: #fff;
+  border-radius: 5px;
+  box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);
+}
+.alert-dialog .alert-title
+{
+  width: 100%;
+  height: 30px;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  box-sizing: border-box;
+  padding: 0 5px;
+  font-size: 1.3em;
+}
+.alert-content
+{
+  width: 100%;
+  height: auto;
+  padding: 8px 10px;
+  text-wrap: avoid;
+  box-sizing: border-box;
+  overflow: auto;
+  max-height: 300px;
+}
+.alert-footer
+{
+  width: 100%;
+  height: 40px;
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+}
+.alert-footer .btn
+{
+  line-height: 30px;
+  text-align: center;
+  border-radius: 5px;
+  margin: 0 5px;
+  cursor: pointer;
+  background-color: #f5f5f5;
+  color: #333;
+}
+.alert-footer .btn:hover
+{
+  background-color: #e6e6e6;
+  color: #666;
+}
+.alert-footer .btn-primary
+{
+  background-color: #409eff;
+  color: #fff;
+}
+</style>

+ 0 - 0
src/components/public/kui-alert-cmd.ts


+ 69 - 8
src/components/public/kui-dialog-cmd.ts

@@ -1,31 +1,35 @@
 import {Component, h, render} from "vue";
+import alertModel from "./alertModel.vue"
 
-enum KuiDialogType {
+export enum KuiDialogType {
     alert,
     confirm,
     prompt,
     loading,
     custom
 }
-interface KuiDialogCmdOptions {
+export interface KuiDialogCmdOptions {
     dialogType?: KuiDialogType;
     showContent: string | Component;
     mountTarget?: string;
     className?: string;
     onClose?: () => void;
     beforeClose?: () => boolean;
+    onOk?: () => void;
     on?: Record<string, (...args: any[]) => void>;
 }
 export class KuiDialogCmd {
     static defaultOptions: KuiDialogCmdOptions = {
         dialogType: KuiDialogType.custom,
         showContent: "kui-dialog",
-        mountTarget: "#kui-root",
+        mountTarget: "kui-root",
         beforeClose: (): boolean => {
             return true;
         },
         onClose() {
         },
+        onOk() {
+        },
         className: "kui-dialog",
     }
     options: KuiDialogCmdOptions;
@@ -46,9 +50,23 @@ export class KuiDialogCmd {
     }
 
     show( props?: any ) {
-        const eventListeners = {
-            onClose: () => this.hide(), // 绑定关闭事件
-        };
+        let eventListeners;
+        if (this.options.dialogType === KuiDialogType.alert) {
+            eventListeners = {
+                onOk: () => {
+                    this.hide();
+                    // 阻断hide触发close事件, 用于在外部
+                    this.options.onOk?.();
+                    },
+                onCancel: () => {
+                    this.hide(true);
+                },
+            }
+        } else {
+            eventListeners = {
+                onClose: () => this.hide(true),
+            };
+        }
         // 解析 option中的on
         // 根据参数决定显示类型
         let vNode =
@@ -72,7 +90,7 @@ export class KuiDialogCmd {
         this.showFlag = true;
     }
 
-    hide() {
+    hide(isSub: boolean = false) {
         let beforeCloseFn = this.options.beforeClose;
         let closeFn = this.options.onClose;
         if (beforeCloseFn && !beforeCloseFn()) {
@@ -84,7 +102,7 @@ export class KuiDialogCmd {
         }
         render(null, this.containerEl);
         this.showFlag = false;
-        if (closeFn) closeFn();
+        if (isSub && closeFn) closeFn();
     }
 
     getAllListeners() {
@@ -95,3 +113,46 @@ export class KuiDialogCmd {
         return this.showFlag;
     }
 }
+
+
+export interface KuiDialogAlertOptions{
+    title: string;
+    content: string;
+    okText?: string;
+    cancelText?: string;
+    onOk?: () => void;
+    onCancel?: () => void;
+    showCancel?: boolean;
+}
+export function showAlert( alertOptions: KuiDialogAlertOptions, mountTarget: string = "kui-root"): KuiDialogCmd
+{
+    const defaultOptions: KuiDialogAlertOptions = {
+        title: "提示",
+        content: "内容",
+        okText: "确定",
+        cancelText: "取消",
+        onOk: () => {},
+        onCancel: () => {},
+        showCancel: true,
+    }
+    let _alertOptions: KuiDialogAlertOptions = {
+        ...defaultOptions,
+        ...alertOptions
+    }
+    let dialogOptions: KuiDialogCmdOptions = {
+        dialogType: KuiDialogType.alert,
+        showContent: alertModel,
+        mountTarget: mountTarget,
+        onOk: _alertOptions.onOk,
+        onClose: _alertOptions.onCancel,
+    }
+    let dialog = new KuiDialogCmd(dialogOptions);
+    dialog.show({
+        title: _alertOptions.title,
+        content: _alertOptions.content,
+        okText: _alertOptions.okText,
+        cancelText: _alertOptions.cancelText,
+        showCancel: _alertOptions.showCancel,
+    });
+    return dialog;
+}

+ 9 - 3
src/main/control/api_router.ts

@@ -1,7 +1,13 @@
 import {ApiType, ErrorCode, RequestData, ResponseData} from "@/types/apiTypes.ts";
 import {Magnet_Actions, Music_Actions} from "@/apis/ApiAction.ts";
 import {c_fetchMagnetList, c_magnet_batch_update, c_magnet_delete} from "@/main/control/magnet/magnet.ts";
-import {c_fetchPlayList, c_scanMusicAdd, c_scanMusicSelect, c_scanSettings} from "@/main/control/magnet/music.ts";
+import {
+    c_fetchPlayList,
+    c_scanMusicAdd, c_scanMusicDelete,
+    c_scanMusicSelect,
+    c_scanMusicUpdate,
+    c_scanSettings
+} from "@/main/control/magnet/music.ts";
 
 export async function apiRouter(requestData: RequestData<any>){
     // 生成callId
@@ -30,10 +36,10 @@ export async function apiRouter(requestData: RequestData<any>){
             responseData = await c_scanSettings(requestData);
             break;
         case Music_Actions.scan_music_update:
-            responseData = await c_scanSettings(requestData);
+            responseData = await c_scanMusicUpdate(requestData);
             break;
         case Music_Actions.scan_music_delete:
-            responseData = await c_scanMusicAdd(requestData);
+            responseData = await c_scanMusicDelete(requestData);
             break;
         default:
             responseData = {

+ 24 - 3
src/main/control/magnet/music.ts

@@ -2,7 +2,13 @@ import {dialog} from "electron"
 import {ApiType, ErrorCode, RequestData, ResponseData} from "@/types/apiTypes.ts";
 import Logger from "@/util/logger.ts";
 import {MusicScanSetting} from "@/types/musicType.ts";
-import {addScanConfig, getScanConfig, getScanConfigByPath, updateScanConfig} from "@/common/db/db_music.ts";
+import {
+    addScanConfig,
+    deleteScanConfig,
+    getScanConfig,
+    getScanConfigByPath,
+    updateScanConfig
+} from "@/common/db/db_music.ts";
 import {ResType} from "@/util/promiseHandle.ts";
 import {t_gen_res, t_res_ok} from "@/main/tools/ipcRouter.ts";
 let logger = Logger.logger('music', 'info');
@@ -98,7 +104,7 @@ export async function c_scanMusicUpdate(requestData: RequestData<MusicScanSettin
     let scanSetting: MusicScanSetting = requestData.data;
     let res: ResType<any> = false;
     // 判断路径是否重复
-    let [err, scanSettingList] = await getScanConfigByPath(scanSetting.path)
+    let [err, scanSettingList] = await getScanConfigByPath(scanSetting.path, [scanSetting.id])
     if (err) {
         logger.error(`[获取扫描设置列表失败] ${err.message}`)
         return t_gen_res(requestData, ErrorCode.db, '获取扫描设置列表失败', false)
@@ -106,6 +112,7 @@ export async function c_scanMusicUpdate(requestData: RequestData<MusicScanSettin
     scanSettingList = scanSettingList as MusicScanSetting[];
     if (scanSettingList.length> 0)
     {
+        // 防止找到正在修改的配置
         logger.error(`[扫描路径重复] ${scanSetting.path}`)
         return t_gen_res(requestData, ErrorCode.params, '扫描路径重复', false)
     }
@@ -117,4 +124,18 @@ export async function c_scanMusicUpdate(requestData: RequestData<MusicScanSettin
     }
     res = res as boolean;
     return t_res_ok(requestData,  res)
-}
+}
+
+
+export async function c_scanMusicDelete(requestData: RequestData<number>) : Promise<ResponseData<boolean>>
+{
+    let res: ResType<any> = false;
+    let err : Error | null = null;
+    [err, res] = await deleteScanConfig(requestData.data);
+    if (err) {
+        logger.error(`[删除扫描设置失败] ${err.message}`)
+        return t_gen_res(requestData, ErrorCode.db, '删除扫描设置失败', false)
+    }
+    res = res as boolean;
+    return t_res_ok(requestData,  res)
+}