多文件功能优化,部分样式调整

pull/1/head
rookie4show 3 weeks ago
parent 5336b57d9b
commit 74d8d55632
  1. 14
      src/App.vue
  2. 73
      src/components/ProjectExplorer.vue
  3. 2
      src/components/Toolbar.vue
  4. 66
      src/components/Yys.vue
  5. 6
      src/data/updateLog.json
  6. 11
      src/main.js
  7. 98
      src/stores/files.ts
  8. 252
      src/ts/files.ts
  9. 14
      src/ts/useGlobalMessage.ts

@ -3,7 +3,7 @@ import Yys from './components/Yys.vue';
import Toolbar from './components/Toolbar.vue';
import ProjectExplorer from './components/ProjectExplorer.vue';
import { computed, ref, onMounted, onUnmounted } from "vue";
import { useFilesStore } from "@/stores/files";
import { useFilesStore } from "@/ts/files";
import Vue3DraggableResizable from 'vue3-draggable-resizable';
import { TabPaneName, TabsPaneContext } from "element-plus";
@ -42,9 +42,10 @@ const handleTabsEdit = (
label: newFileName,
name: newFileName,
visible: true,
type:'PVE',
groups:[
{
shortDescription: '',
shortDescription: " ",
groupInfo: [{}, {}, {}, {}, {}],
details: ''
},
@ -98,12 +99,12 @@ const activeFileGroups = computed(() => {
>
<el-tab-pane
v-for="(file, index) in filesStore.visibleFiles"
:key="index"
:key="`${file.name}-${filesStore.activeFile}`"
:label="file.label"
:name="file.name.toString()"
>
<main id="main-container" :style="{ height: contentHeight, overflow: 'auto' }">
<Yys :groups="activeFileGroups" ref="yysRef"/>
<Yys :groups="activeFileGroups"/>
</main>
</el-tab-pane>
</el-tabs>
@ -133,15 +134,16 @@ const activeFileGroups = computed(() => {
}
.sidebar {
width: 200px; /* 侧边栏宽度 */
width: 20%; /* 侧边栏宽度 */
background-color: #f0f0f0; /* 背景色 */
flex-shrink: 0; /* 防止侧边栏被压缩 */
overflow-y: auto; /* 允许侧边栏内容滚动 */
}
.workspace {
flex: 1; /* 占据剩余空间 */
//flex: 1; /* */
overflow: hidden; /* 防止内容溢出 */
display: inline-block;
}
#main-container {

@ -4,13 +4,38 @@
:data="allFiles"
:props="defaultProps"
@node-click="handleNodeClick"
/>
node-key="name"
default-expand-all
:expand-on-click-node="false"
>
<template #default="{ node, data }">
<div class="custom-tree-node">
<span>{{ node.label }}</span>
<div>
<el-dropdown>
<el-button
style="margin-left: 4px"
icon="MoreFilled"
link
/>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="() => renameFile(data)">Rename</el-dropdown-item>
<el-dropdown-item @click="() => deleteFile(data)">Delete</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
</el-tree>
</div>
</template>
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue';
import { useFilesStore } from "@/stores/files";
import {defineProps, defineEmits, ref} from 'vue';
import {useFilesStore} from "@/ts/files";
import {ElTree, ElButton, ElDropdownMenu, ElDropdownItem} from 'element-plus';
const filesStore = useFilesStore();
@ -21,21 +46,61 @@ const props = defineProps({
},
});
const defaultProps = {
children: 'children',
label: 'label',
};
const visibleDropdown = ref(false);
const dropdownLeft = ref(0);
const dropdownTop = ref(0);
const selectedData = ref(null);
const handleNodeClick = (data) => {
filesStore.setActiveFile(data.name);
filesStore.setVisible(data.name, true);
};
const showOptions = (node, data) => {
visibleDropdown.value = true;
dropdownLeft.value = node.$el.offsetLeft + 20;
dropdownTop.value = node.$el.offsetTop + 20;
selectedData.value = data;
};
const hideDropdown = () => {
visibleDropdown.value = false;
};
document.addEventListener('click', hideDropdown);
const deleteFile = (data) => {
filesStore.deleteFile(data.name);
hideDropdown();
};
const renameFile = (data) => {
const newName = prompt("Enter new name:", data.label);
if (newName && newName !== data.label) {
filesStore.renameFile(data.name, newName);
}
hideDropdown();
};
</script>
<style scoped>
.project-explorer {
height: 50%; /* 占据侧边栏的一半 */
min-height: 400px;
overflow-y: auto; /* 允许内容滚动 */
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
}
</style>

@ -83,7 +83,7 @@ import {ref, reactive} from 'vue';
import html2canvas from "html2canvas";
import {useI18n} from 'vue-i18n';
import updateLogs from "../data/updateLog.json"
import {useFilesStore} from "@/stores/files";
import {useFilesStore} from "@/ts/files";
const filesStore = useFilesStore();

@ -122,7 +122,7 @@ import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import shikigamiData from '../data/Shikigami.json';
import _ from 'lodash';
import {Action, ElMessage, ElMessageBox} from "element-plus";
import { useGlobalMessage } from '../ts/useGlobalMessage'; //
const props = defineProps<{
groups: any[];
}>();
@ -141,6 +141,7 @@ const state = reactive({
const clipboard = ref('');
const { showMessage } = useGlobalMessage();
// i18n
const {t} = useI18n()
@ -217,60 +218,45 @@ const updateProperty = (property) => {
props.groups[state.groupIndex].groupInfo[state.positionIndex].properties = _.cloneDeep(property);
};
const removeGroupElement = (groupIndex, positionIndex) => {
const removeGroupElement = async (groupIndex: number, positionIndex: number) => {
const group = props.groups[groupIndex];
if (group.groupInfo.length === 1) {
ElMessageBox.alert('无法删除', '提示', {
confirmButtonText: '确定',
});
showMessage('warning', '无法删除');
return;
}
ElMessageBox.confirm('确定要删除此元素吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
group.groupInfo.splice(positionIndex, 1);
ElMessage({
type: 'success',
message: '删除成功!',
});
}).catch(() => {
ElMessage({
type: 'info',
message: '已取消删除',
try {
await ElMessageBox.confirm('确定要删除此元素吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
});
});
group.groupInfo.splice(positionIndex, 1);
showMessage('success', '删除成功!');
} catch (error) {
showMessage('info', '已取消删除');
}
};
const removeGroup = (groupIndex) => {
const removeGroup = async (groupIndex: number) => {
if (props.groups.length === 1) {
ElMessageBox.alert('无法删除最后一个队伍', '提示', {
confirmButtonText: '确定',
});
showMessage('warning', '无法删除最后一个队伍');
return;
}
ElMessageBox.confirm('确定要删除此组吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
props.groups.splice(groupIndex, 1);
ElMessage({
type: 'success',
message: '删除成功!',
});
}).catch(() => {
ElMessage({
type: 'info',
message: '已取消删除',
try {
await ElMessageBox.confirm('确定要删除此组吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
});
});
props.groups.splice(groupIndex, 1);
showMessage('success', '删除成功!');
} catch (error) {
showMessage('info', '已取消删除');
}
};
const addGroup = () => {
props.groups.push({
shortDescription: '',

@ -1,11 +1,11 @@
[
{
"version": "2.0.0",
"date": "2025-03-16",
"date": "2025-03-18",
"changes": [
"修复了相同式神不能正确设置属性的问题",
"支持了多文件编辑",
"PS:当前导出截图宽度无法"
"支持了多文件编辑,重命名,删除效果",
"增加了最后一个文件/队伍/式神的删除限制"
]
},
{

@ -1,7 +1,7 @@
import { createApp } from 'vue'
import App from './App.vue'
import ElementPlus from 'element-plus'
import ElementPlus, {ElMessageBox} from 'element-plus'
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
@ -44,6 +44,15 @@ const i18n = createI18n({
},
})
// 设置ElMessageBox的默认配置
ElMessageBox.defaults = {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning', // 默认类型为警告
center: true, // 文字居中
customClass: 'my-message-box', // 自定义类名,用于CSS样式覆盖
};
const pinia = createPinia() // 创建 Pinia 实例
app.use(pinia) // 使用 Pinia

@ -1,98 +0,0 @@
import {defineStore} from 'pinia';
export const useFilesStore = defineStore('files', {
state: () => ({
fileList: [
{
label: 'Welcome',
name: "1",
visible: true,
groups: [
{
shortDescription: '<h1>鬼灵歌姬</h1><p>这是一个演示项目,用于测试显示效果</p>',
groupInfo: [{
"avatar": "/assets/Shikigami/sp/372.png",
"name": "因幡辉夜姬",
"rarity": "SP"
},
{
"avatar": "/assets/Shikigami/ssr/356.png",
"name": "千姬",
"rarity": "SSR"
},
{
"avatar": "/assets/Shikigami/sp/554.png",
"name": "纺愿缘结神",
"rarity": "SP"
},
{
"avatar": "/assets/Shikigami/ssr/556.png",
"name": "天照",
"rarity": "SSR"
},
{
"avatar": "/assets/Shikigami/ssr/557.png",
"name": "伊邪那美",
"rarity": "SSR"
},
{
"avatar": "/assets/Shikigami/sp/367.png",
"name": "绘世花鸟卷",
"rarity": "SP"
}],
details: ''
},
{
shortDescription: '',
groupInfo: [{}, {}, {}, {}, {}],
details: ''
}
]
}, {
label: 'File 2',
name: "2",
visible: true,
groups:[
{
shortDescription: '',
groupInfo: [{}, {}, {}, {}, {}],
details: ''
},
{
shortDescription: '',
groupInfo: [{}, {}, {}, {}, {}],
details: ''
}
]
}],
activeFile: "1",
}),
getters: {
visibleFiles: (state) => state.fileList.filter(file => file.visible),
},
actions: {
addFile(file) {
this.fileList.push({...file, visible: true});
this.activeFile = file.name;
},
setActiveFile(fileId: number) {
this.activeFile = fileId;
},
setVisible(fileId: number, visibility: boolean) {
const file = this.fileList.find(file => file.name === fileId);
if (file) {
file.visible = visibility;
}
},
closeTab(fileName: String) {
const file = this.fileList.find(file => file.name === fileName);
if (file) {
file.visible = false;
if (this.activeFile === fileName) {
const nextVisibleFile = this.visibleFiles[0];
this.activeFile = nextVisibleFile ? nextVisibleFile.name : -1;
}
}
},
},
});

@ -0,0 +1,252 @@
import {defineStore} from 'pinia';
import {ElMessageBox} from "element-plus";
import {useGlobalMessage} from "./useGlobalMessage";
const { showMessage } = useGlobalMessage();
export const useFilesStore = defineStore('files', {
state: () => ({
fileList: [
{
"label": "Welcome",
"name": "1",
"type":"PVE",
"visible": true,
"groups": [
{
"shortDescription": "<h1>鬼灵歌姬</h1><h2><em>这是一个演示项目,用于测试显示6个式神的对齐效果</em></h2>",
"groupInfo": [
{
"avatar": "/assets/Shikigami/sp/372.png",
"name": "因幡辉夜姬",
"rarity": "SP"
},
{
"avatar": "/assets/Shikigami/ssr/356.png",
"name": "千姬",
"rarity": "SSR"
},
{
"avatar": "/assets/Shikigami/sp/554.png",
"name": "纺愿缘结神",
"rarity": "SP"
},
{
"avatar": "/assets/Shikigami/ssr/556.png",
"name": "天照",
"rarity": "SSR"
},
{
"avatar": "/assets/Shikigami/ssr/557.png",
"name": "伊邪那美",
"rarity": "SSR"
},
{
"avatar": "/assets/Shikigami/sp/367.png",
"name": "绘世花鸟卷",
"rarity": "SP"
}
],
"details": "<h2><strong>开局因幡普攻主怪后锁二</strong></h2>"
},
{
"shortDescription": "<h1>魂土15秒</h1><p><em>相同式神编辑不同属性</em></p>",
"groupInfo": [
{
"avatar": "/assets/Shikigami/ssr/364.png",
"name": "阿修罗",
"rarity": "SSR",
"properties": {
"edit": true,
"yuhun": {
"yuhunSetEffect": [
{
"name": "狂骨",
"shortName": "狂",
"type": "attack",
"avatar": "/assets/Yuhun/狂骨.png"
},
{
"name": "荒骷髅",
"shortName": "荒",
"type": "PVE",
"avatar": "/assets/Yuhun/荒骷髅.png"
}
],
"target": "1",
"property2": [
"Attack"
],
"property4": [
"Attack"
],
"property6": [
"Crit",
"CritDamage"
]
},
"levelRequired": "40",
"speed": "",
"skillRequiredMode": "all",
"skillRequired": [
"5",
"5",
"5"
]
}
},
{
"avatar": "/assets/Shikigami/ssr/364.png",
"name": "阿修罗",
"rarity": "SSR",
"properties": {
"edit": true,
"yuhun": {
"yuhunSetEffect": [
{
"name": "狂骨",
"shortName": "狂",
"type": "attack",
"avatar": "/assets/Yuhun/狂骨.png"
},
{
"name": "鬼灵歌伎",
"shortName": "歌伎",
"type": "PVE",
"avatar": "/assets/Yuhun/鬼灵歌伎.png"
}
],
"target": "0",
"property2": [
"Attack"
],
"property4": [
"Attack"
],
"property6": [
"Crit",
"CritDamage"
]
},
"levelRequired": "40",
"speed": "",
"skillRequiredMode": "all",
"skillRequired": [
"5",
"5",
"5"
]
}
},
{
"avatar": "/assets/Shikigami/ssr/370.png",
"name": "饭笥",
"rarity": "SSR"
},
{
"avatar": "/assets/Shikigami/ssr/369.png",
"name": "食灵",
"rarity": "SSR"
},
{
"avatar": "/assets/Shikigami/r/205.png",
"name": "座敷童子",
"rarity": "R"
}
],
"details": ""
}
]
},
{
"label": "Test",
"name": "2",
"visible": true,
"type":"PVE",
"groups": [
{
"shortDescription": "<h1>御魂·悲鸣</h1><p>这是一个演示项目,用于测试不同标签页的切换效果</p>",
"groupInfo": [
{},
{},
{},
{},
{}
],
"details": ""
},
{
"shortDescription": "",
"groupInfo": [
{},
{},
{},
{},
{}
],
"details": ""
}
]
}
],
activeFile: "1",
}),
getters: {
visibleFiles: (state) => state.fileList.filter(file => file.visible),
},
actions: {
addFile(file) {
this.fileList.push({...file, visible: true});
this.activeFile = file.name;
},
setActiveFile(fileId: number) {
this.activeFile = fileId;
},
setVisible(fileId: number, visibility: boolean) {
const file = this.fileList.find(file => file.name === fileId);
if (file) {
file.visible = visibility;
}
},
closeTab(fileName: String) {
const file = this.fileList.find(file => file.name === fileName);
if (file) {
file.visible = false;
if (this.activeFile === fileName) {
const nextVisibleFile = this.visibleFiles[0];
this.activeFile = nextVisibleFile ? nextVisibleFile.name : -1;
}
}
},
async deleteFile(fileId: string) {
try {
if (this.fileList.length === 1) {
showMessage('warning', '无法删除');
return;
}
await ElMessageBox.confirm('确定要删除此文件吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
});
const index = this.fileList.findIndex(file => file.name === fileId);
if (index > -1) {
this.fileList.splice(index, 1);
if (this.activeFile === fileId) {
const nextVisibleFile = this.visibleFiles[0];
this.activeFile = nextVisibleFile ? nextVisibleFile.name : "-1";
}
}
showMessage('success', '删除成功!');
} catch (error) {
showMessage('info', '已取消删除');
}
},
renameFile(fileId, newName) {
const file = this.fileList.find(file => file.name === fileId);
if (file) {
file.label = newName;
}
},
},
});

@ -0,0 +1,14 @@
import { ElMessage } from 'element-plus';
export function useGlobalMessage() {
const showMessage = (type: 'success' | 'warning' | 'info' | 'error', message: string) => {
ElMessage({
type,
message,
});
};
return {
showMessage,
};
}
Loading…
Cancel
Save