多文件支持

pull/1/head
rookie4show 1 month ago
parent 60091e88a2
commit 8839c37256
  1. BIN
      public/assets/Other/Contact.png
  2. 72
      src/App.vue
  3. 6
      src/components/ProjectExplorer.vue
  4. 3
      src/components/ShikigamiProperty.vue
  5. 66
      src/components/Toolbar.vue
  6. 60
      src/components/Yys.vue
  7. 61
      src/stores/files.ts

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

@ -5,7 +5,7 @@ import ProjectExplorer from './components/ProjectExplorer.vue';
import { computed, ref, onMounted, onUnmounted } from "vue"; import { computed, ref, onMounted, onUnmounted } from "vue";
import { useFilesStore } from "@/stores/files"; import { useFilesStore } from "@/stores/files";
import Vue3DraggableResizable from 'vue3-draggable-resizable'; import Vue3DraggableResizable from 'vue3-draggable-resizable';
import {TabPaneName, TabsPaneContext} from "element-plus"; import { TabPaneName, TabsPaneContext } from "element-plus";
const filesStore = useFilesStore(); const filesStore = useFilesStore();
@ -21,12 +21,49 @@ const onResizing = (x, y, width, height) => {
height.value = height; height.value = height;
}; };
const handleExport = () => {
const dataStr = JSON.stringify(filesStore.fileList, null, 2);
const blob = new Blob([dataStr], { type: 'application/json;charset=utf-8' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = 'files.json';
link.click();
URL.revokeObjectURL(url);
};
const onHandleInport = (file) => { const onHandleInport = (file) => {
yysRef.value.importGroups(file); const reader = new FileReader();
reader.onload = (e) => {
try {
const data = JSON.parse(e.target.result as string);
if (data[0].visible === true) {
// fileList
filesStore.$patch({ fileList: data });
} else {
// groups
const newFile = {
label: `File ${filesStore.fileList.length + 1}`,
name: String(filesStore.fileList.length + 1),
visible: true,
groups: data
};
filesStore.addFile(newFile);
}
} catch (error) {
console.error('Failed to import file', error);
}
};
reader.readAsText(file);
}; };
// const onHandleInport = (file) => {
//
// handleImport(file);
// };
const onHandleExport = () => { const onHandleExport = () => {
yysRef.value.exportGroups(); handleExport();
}; };
const element = ref({ const element = ref({
@ -39,20 +76,18 @@ const element = ref({
const handleFileSelected = (fileId) => { const handleFileSelected = (fileId) => {
filesStore.setActiveFile(fileId); filesStore.setActiveFile(fileId);
filesStore.setVisible(fileId, true);
}; };
const handleTabsEdit = ( const handleTabsEdit = (
targetName: TabPaneName | undefined, targetName: String | undefined,
action: 'remove' | 'add' action: 'remove' | 'add'
)=> { ) => {
const tabIndex = filesStore.fileList.findIndex(file => file.name === parseInt(name.toString())); if (action === 'remove') {
if (tabIndex !== -1) { filesStore.closeTab(targetName);
filesStore.fileList.splice(tabIndex, 1); } else if (action === 'add') {
if (filesStore.fileList.length > 0) { const newFileName = `File ${filesStore.fileList.length + 1}`;
filesStore.setActiveFile(filesStore.fileList[0].name); filesStore.addFile({ label: newFileName, name: newFileName });
} else {
filesStore.setActiveFile(-1); //
}
} }
}; };
@ -67,6 +102,11 @@ onUnmounted(() => {
windowHeight.value = window.innerHeight; windowHeight.value = window.innerHeight;
}); });
}); });
const activeFileGroups = computed(() => {
const activeFile = filesStore.fileList.find(file => file.name === filesStore.activeFile);
return activeFile ? activeFile.groups : [];
});
</script> </script>
<template> <template>
@ -77,7 +117,7 @@ onUnmounted(() => {
<div class="main-content"> <div class="main-content">
<!-- 侧边栏 --> <!-- 侧边栏 -->
<aside class="sidebar"> <aside class="sidebar">
<ProjectExplorer :files="filesStore.fileList" @file-selected="handleFileSelected" /> <ProjectExplorer :allFiles="filesStore.fileList" @file-selected="handleFileSelected" />
</aside> </aside>
<!-- 工作区 --> <!-- 工作区 -->
@ -91,12 +131,12 @@ onUnmounted(() => {
@edit="handleTabsEdit" @edit="handleTabsEdit"
> >
<el-tab-pane <el-tab-pane
v-for="(file, index) in filesStore.fileList" v-for="(file, index) in filesStore.visibleFiles"
:key="index" :key="index"
:label="file.label" :label="file.label"
:name="file.name.toString()" :name="file.name.toString()"
> >
<Yys ref="yysRef" /> <Yys :groups="activeFileGroups" ref="yysRef" />
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
</main> </main>

@ -1,7 +1,7 @@
<template> <template>
<div class="project-explorer"> <div class="project-explorer">
<el-tree <el-tree
:data="files" :data="allFiles"
:props="defaultProps" :props="defaultProps"
@node-click="handleNodeClick" @node-click="handleNodeClick"
/> />
@ -9,10 +9,10 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { defineProps, defineEmits } from 'vue';
const props = defineProps({ const props = defineProps({
files: { allFiles: {
type: Array, type: Array,
required: true, required: true,
}, },

@ -130,6 +130,7 @@ import YuhunSelect from "@/components/YuhunSelect.vue";
import {useI18n} from 'vue-i18n' import {useI18n} from 'vue-i18n'
// import YuhunSelect from "./YuhunSelect.vue"; // import YuhunSelect from "./YuhunSelect.vue";
// i18n // i18n
const {t} = useI18n() const {t} = useI18n()
@ -339,7 +340,7 @@ const cancel = () => {
const confirm = () => { const confirm = () => {
shikigami.value.edit = true shikigami.value.edit = true
emit('updateProperty', JSON.parse(JSON.stringify(shikigami.value))) emit('updateProperty', shikigami.value);
resetData() resetData()
} }

@ -8,14 +8,37 @@
t('setWatermark') t('setWatermark')
}} }}
</el-button> </el-button>
<!-- 新增的按钮 -->
<el-button type="info" @click="showUpdateLog">更新日志</el-button>
<el-button type="warning" @click="showFeedbackForm">问题反馈</el-button>
</div> </div>
<!-- 更新日志对话框 -->
<el-dialog v-model="state.showUpdateLogDialog" title="更新日志" width="60%">
<ul>
<li v-for="(log, index) in updateLogs" :key="index">
<strong>版本 {{ log.version }} - {{ log.date }}</strong>
<ul>
<li v-for="(change, idx) in log.changes" :key="idx">{{ change }}</li>
</ul>
</li>
</ul>
</el-dialog>
<!-- 更新日志对话框 -->
<el-dialog v-model="state.showFeedbackFormDialog" title="更新日志" width="60%">
<span style="font-size: 24px;">备注阴阳师</span>
<br/>
<img src="/assets/Other/Contact.png"
style="cursor: pointer; vertical-align: bottom; width: 200px; height: auto;"/>
</el-dialog>
<!-- 预览弹窗 --> <!-- 预览弹窗 -->
<el-dialog id="preview-container" v-model="state.previewVisible" width="80%" height="80%" :before-close="handleClose"> <el-dialog id="preview-container" v-model="state.previewVisible" width="80%" height="80%"
:before-close="handleClose">
<div style="max-height: 500px; overflow-y: auto;"> <div style="max-height: 500px; overflow-y: auto;">
<img v-if="state.previewImage" :src="state.previewImage" alt="Preview" style="width: 100%; display: block;"/> <img v-if="state.previewImage" :src="state.previewImage" alt="Preview" style="width: 100%; display: block;"/>
</div> </div>
<!-- <img v-if="state.previewImage" :src="state.previewImage" alt="Preview" style="width: 100%; height: auto;" />-->
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
<el-button @click="state.previewVisible = false"> </el-button> <el-button @click="state.previewVisible = false"> </el-button>
<el-button type="primary" @click="downloadImage"> </el-button> <el-button type="primary" @click="downloadImage"> </el-button>
@ -62,15 +85,46 @@ import {useI18n} from 'vue-i18n';
// i18n // i18n
const {t} = useI18n(); const {t} = useI18n();
const emit = defineEmits(['handleExport', 'handleImport']) const emit = defineEmits(['handleExport', 'handleImport']);
// //
const state = reactive({ const state = reactive({
previewImage: null, // URL previewImage: null, // URL
previewVisible: false, // previewVisible: false, //
showWatermarkDialog: false, // showWatermarkDialog: false, // ,
showUpdateLogDialog: false, //
showFeedbackFormDialog: false, //
}); });
//
const updateLogs = [
{
version: '2.0.0',
date: '2025-03-16',
changes: [
'修复了相同式神不能正确设置属性的问题',
'支持了多文件编辑',
'PS:当前导出截图宽度无法'
]
},
{
version: '1.0.0',
date: '2025-03-09',
changes: [
'首次发布'
]
},
];
const showUpdateLog = () => {
state.showUpdateLogDialog = !state.showUpdateLogDialog;
};
const showFeedbackForm = () => {
state.showFeedbackFormDialog = !state.showFeedbackFormDialog;
};
const handleExport = () => { const handleExport = () => {
emit('handleExport'); emit('handleExport');
}; };
@ -86,7 +140,6 @@ const handleImport = () => {
input.click(); input.click();
}; };
const watermark = reactive({ const watermark = reactive({
text: '示例水印', text: '示例水印',
fontSize: 30, fontSize: 30,
@ -150,7 +203,6 @@ function calculateVisualHeight(selector) {
return rows.reduce((sum, row) => sum + row.maxHeight, 0); return rows.reduce((sum, row) => sum + row.maxHeight, 0);
} }
const ignoreElements = (element) => { const ignoreElements = (element) => {
return element.classList.contains('ql-toolbar') || element.classList.contains('el-tabs__header'); return element.classList.contains('ql-toolbar') || element.classList.contains('el-tabs__header');
}; };
@ -248,6 +300,7 @@ const prepareCapture = async () => {
document.head.removeChild(style); document.head.removeChild(style);
} }
}; };
const downloadImage = () => { const downloadImage = () => {
if (state.previewImage) { if (state.previewImage) {
const link = document.createElement('a'); const link = document.createElement('a');
@ -272,7 +325,6 @@ const handleClose = (done) => {
right: 0; right: 0;
height: 48px; height: 48px;
background: #f8f8f8; background: #f8f8f8;
//border-bottom: 1px solid #eee; display: flex;
align-items: center; align-items: center;
padding: 0 8px; padding: 0 8px;
z-index: 100; z-index: 100;

@ -14,7 +14,7 @@
/> />
<draggable :list="state.groups" item-key="group" style="display: flex; flex-direction: column; width: 100%;" <draggable :list="groups" item-key="group" style="display: flex; flex-direction: column; width: 100%;"
handle=".drag-handle"> handle=".drag-handle">
@ -126,7 +126,7 @@
</div> </div>
</template> </template>
<script setup> <script setup lang="ts">
import {ref, reactive, toRefs} from 'vue'; import {ref, reactive, toRefs} from 'vue';
import draggable from 'vuedraggable'; import draggable from 'vuedraggable';
import ShikigamiSelect from './ShikigamiSelect.vue'; import ShikigamiSelect from './ShikigamiSelect.vue';
@ -139,35 +139,15 @@ import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import shikigamiData from '../data/Shikigami.json'; import shikigamiData from '../data/Shikigami.json';
import _ from 'lodash'; import _ from 'lodash';
const props = defineProps<{
groups: any[];
}>();
const dialogTableVisible = ref(false) const dialogTableVisible = ref(false)
// //
const state = reactive({ const state = reactive({
showSelectShikigami: false, showSelectShikigami: false,
showProperty: false, showProperty: false,
groups: [
{
shortDescription: '',
groupInfo: [{}, {}, {}, {}, {}],
details: ''
},
{
shortDescription: '',
groupInfo: [{}, {}, {}, {}, {}],
details: ''
},{
shortDescription: '',
groupInfo: [{}, {}, {}, {}, {}],
details: ''
},{
shortDescription: '',
groupInfo: [{}, {}, {}, {}, {}],
details: ''
},{
shortDescription: '',
groupInfo: [{}, {}, {}, {}, {}],
details: ''
},
],
groupIndex: 0, groupIndex: 0,
positionIndex: 0, positionIndex: 0,
currentShikigami: {}, currentShikigami: {},
@ -187,9 +167,9 @@ const copy = (str) => {
const paste = (groupIndex, type) => { const paste = (groupIndex, type) => {
if ('shortDescription' == type) if ('shortDescription' == type)
state.groups[groupIndex].shortDescription = clipboard.value props.groups[groupIndex].shortDescription = clipboard.value
else if ('details' == type) else if ('details' == type)
state.groups[groupIndex].details = clipboard.value props.groups[groupIndex].details = clipboard.value
} }
// //
@ -223,23 +203,23 @@ const editShikigami = (groupIndex, positionIndex) => {
state.showSelectShikigami = true; state.showSelectShikigami = true;
state.groupIndex = groupIndex; state.groupIndex = groupIndex;
state.positionIndex = positionIndex; state.positionIndex = positionIndex;
state.currentShikigami = state.groups[groupIndex].groupInfo[positionIndex]; state.currentShikigami = props.groups[groupIndex].groupInfo[positionIndex];
}; };
const updateShikigami = (shikigami) => { const updateShikigami = (shikigami) => {
console.log("parent====> ", shikigami); console.log("parent====> ", shikigami);
state.showSelectShikigami = false; state.showSelectShikigami = false;
const oldProperties = state.groups[state.groupIndex].groupInfo[state.positionIndex].properties; const oldProperties = props.groups[state.groupIndex].groupInfo[state.positionIndex].properties;
state.groups[state.groupIndex].groupInfo[state.positionIndex] = _.cloneDeep(shikigami); props.groups[state.groupIndex].groupInfo[state.positionIndex] = _.cloneDeep(shikigami);
state.groups[state.groupIndex].groupInfo[state.positionIndex].properties = oldProperties; props.groups[state.groupIndex].groupInfo[state.positionIndex].properties = oldProperties;
}; };
const editProperty = (groupIndex, positionIndex) => { const editProperty = (groupIndex, positionIndex) => {
state.showProperty = true; state.showProperty = true;
state.groupIndex = groupIndex; state.groupIndex = groupIndex;
state.positionIndex = positionIndex; state.positionIndex = positionIndex;
state.currentShikigami = state.groups[groupIndex].groupInfo[positionIndex]; state.currentShikigami = props.groups[groupIndex].groupInfo[positionIndex];
}; };
const closeProperty = () => { const closeProperty = () => {
@ -250,19 +230,19 @@ const closeProperty = () => {
const updateProperty = (property) => { const updateProperty = (property) => {
state.showProperty = false; state.showProperty = false;
state.currentShikigami = {}; state.currentShikigami = {};
state.groups[state.groupIndex].groupInfo[state.positionIndex].properties = _.cloneDeep(property); props.groups[state.groupIndex].groupInfo[state.positionIndex].properties = _.cloneDeep(property);
}; };
const removeGroupElement = (groupIndex, positionIndex) => { const removeGroupElement = (groupIndex, positionIndex) => {
state.groups[groupIndex].groupInfo.splice(positionIndex, 1); props.groups[groupIndex].groupInfo.splice(positionIndex, 1);
}; };
const removeGroup = (groupIndex) => { const removeGroup = (groupIndex) => {
state.groups.splice(groupIndex, 1); props.groups.splice(groupIndex, 1);
}; };
const addGroup = () => { const addGroup = () => {
state.groups.push({ props.groups.push({
shortDescription: '', shortDescription: '',
groupInfo: [{}, {}, {}, {}, {}], groupInfo: [{}, {}, {}, {}, {}],
details: '' details: ''
@ -270,12 +250,12 @@ const addGroup = () => {
}; };
const addGroupElement = (groupIndex) => { const addGroupElement = (groupIndex) => {
state.groups[groupIndex].groupInfo.push({}); props.groups[groupIndex].groupInfo.push({});
}; };
const exportGroups = () => { const exportGroups = () => {
const dataStr = JSON.stringify(state.groups, null, 2); const dataStr = JSON.stringify(props.groups, null, 2);
const blob = new Blob([dataStr], {type: 'application/json'}); const blob = new Blob([dataStr], {type: 'application/json'});
const url = URL.createObjectURL(blob); const url = URL.createObjectURL(blob);
const link = document.createElement('a'); const link = document.createElement('a');
@ -326,7 +306,7 @@ const importGroups = (file) => {
reader.onload = (e) => { reader.onload = (e) => {
try { try {
const importedData = JSON.parse(e.target.result); const importedData = JSON.parse(e.target.result);
state.groups = importedData; props.groups = importedData;
ElMessage.success('导入成功'); ElMessage.success('导入成功');
} catch (error) { } catch (error) {
ElMessage.error('文件格式错误'); ElMessage.error('文件格式错误');

@ -1,16 +1,69 @@
import { defineStore } from 'pinia'; import {defineStore} from 'pinia';
export const useFilesStore = defineStore('files', { export const useFilesStore = defineStore('files', {
state: () => ({ state: () => ({
fileList: [{ label: 'File 1', name: 1 },{ label: 'File 2', name: 2 }], fileList: [
activeFile: 1, {
label: 'File 1',
name: "1",
visible: true,
groups: [
{
shortDescription: '',
groupInfo: [{}, {}, {}, {}, {}],
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: { actions: {
addFile(file: { label: string; name: number }) { addFile(file: { label: string; name: number }) {
this.fileList.push(file); this.fileList.push({...file, visible: true});
this.activeFile = file.name;
}, },
setActiveFile(fileId: number) { setActiveFile(fileId: number) {
this.activeFile = fileId; 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;
}
}
},
}, },
}); });
Loading…
Cancel
Save