@smartos-lib/components 1.7.0-beta.0
Sign up to get free protection for your applications and to get access to all the features.
- package/.eslintrc +12 -0
- package/.eslintrc-auto-import.json +332 -0
- package/Components.code-workspace +143 -0
- package/LICENSE +21 -0
- package/dist/smart-docx-editor/index.d.ts +2 -0
- package/dist/smart-docx-editor/index.js +68 -0
- package/dist/smart-file-preview/index.d.ts +18 -0
- package/dist/smart-file-preview/index.js +37 -0
- package/dist/smart-upload/index.d.ts +2 -0
- package/dist/smart-upload/index.js +800 -0
- package/index.html +16 -0
- package/package.json +23 -0
- package/public/favicon.svg +6 -0
- package/scripts/components.vite.config.ts +96 -0
- package/scripts/shared.ts +9 -0
- package/src/App.vue +28 -0
- package/src/components/Logo/index.vue +15 -0
- package/src/components-private/.gitkeep +0 -0
- package/src/composables/useElementStyle.ts +23 -0
- package/src/composables/useNaiveStyle.ts +43 -0
- package/src/composables/useNaiveTheme.ts +71 -0
- package/src/composables/useSmart.ts +36 -0
- package/src/layouts/default.vue +3 -0
- package/src/main.ts +33 -0
- package/src/modules/pinia/index.ts +8 -0
- package/src/modules/progress/index.ts +12 -0
- package/src/modules/router/install.ts +9 -0
- package/src/modules/router/routes.ts +40 -0
- package/src/pages/[...all].vue +21 -0
- package/src/pages/frame/component/[name].vue +14 -0
- package/src/pages/frame/index.vue +81 -0
- package/src/pages/index/composables/useTabsManage.ts +46 -0
- package/src/pages/index/index.vue +111 -0
- package/src/pages/index/type.ts +13 -0
- package/src/pages/index/utils/index.ts +41 -0
- package/src/settings.ts +9 -0
- package/src/shared/components.ts +52 -0
- package/src/shared/env.ts +11 -0
- package/src/shared/unocss.theme.ts +1600 -0
- package/src/stores/theme.ts +29 -0
- package/src/styles/element.scss +3 -0
- package/src/styles/styles.scss +21 -0
- package/src/types.ts +20 -0
- package/src/utils/callCustomElementExposed.ts +6 -0
- package/src/utils/deepCloneESModule.ts +10 -0
- package/src/utils/defineCustomElements.ts +18 -0
- package/src/utils/formatComponentsGlob.ts +16 -0
- package/src/utils/getFileMD5.ts +31 -0
- package/src/utils/getFileNameAndExt.ts +11 -0
- package/src/utils/isFileEqual.ts +13 -0
- package/src/utils/jsonToFormData.ts +8 -0
- package/src/web-components/smart-docx-drive-page/App.vue +37 -0
- package/src/web-components/smart-docx-drive-page/apis/doc.ts +85 -0
- package/src/web-components/smart-docx-drive-page/apis/file.ts +278 -0
- package/src/web-components/smart-docx-drive-page/apis/folder.ts +72 -0
- package/src/web-components/smart-docx-drive-page/children/Home.vue +8 -0
- package/src/web-components/smart-docx-drive-page/children/Me.vue +47 -0
- package/src/web-components/smart-docx-drive-page/components/CustomImage.vue +26 -0
- package/src/web-components/smart-docx-drive-page/components/CustomPopover.vue +62 -0
- package/src/web-components/smart-docx-drive-page/components/DocxDir.vue +99 -0
- package/src/web-components/smart-docx-drive-page/components/DocxDoc.vue +132 -0
- package/src/web-components/smart-docx-drive-page/components/DocxDownloadPopoverItem.vue +41 -0
- package/src/web-components/smart-docx-drive-page/components/DocxFileList.vue +156 -0
- package/src/web-components/smart-docx-drive-page/components/DocxPreview.vue +33 -0
- package/src/web-components/smart-docx-drive-page/components/DocxUpload.vue +164 -0
- package/src/web-components/smart-docx-drive-page/components/FileIcon.vue +62 -0
- package/src/web-components/smart-docx-drive-page/components-private/Header.vue +65 -0
- package/src/web-components/smart-docx-drive-page/components-private/Logo.vue +15 -0
- package/src/web-components/smart-docx-drive-page/components-private/Menu.vue +34 -0
- package/src/web-components/smart-docx-drive-page/components-private/Navbar.vue +36 -0
- package/src/web-components/smart-docx-drive-page/composables/useFullscreenElDialog.ts +41 -0
- package/src/web-components/smart-docx-drive-page/composables/usePrompt.ts +73 -0
- package/src/web-components/smart-docx-drive-page/data.ts +10 -0
- package/src/web-components/smart-docx-drive-page/external-style/custom-popover.sass +8 -0
- package/src/web-components/smart-docx-drive-page/external-style/index.sass +1 -0
- package/src/web-components/smart-docx-drive-page/index.ts +20 -0
- package/src/web-components/smart-docx-drive-page/index.vue +39 -0
- package/src/web-components/smart-docx-drive-page/info.ts +2 -0
- package/src/web-components/smart-docx-drive-page/stores/menu.ts +60 -0
- package/src/web-components/smart-docx-drive-page/types.ts +51 -0
- package/src/web-components/smart-docx-drive-page/utils/file-actions.ts +63 -0
- package/src/web-components/smart-docx-drive-page/utils/file.ts +31 -0
- package/src/web-components/smart-docx-editor/App.vue +32 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/components/Markdown.vue +202 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/components/Menu.vue +100 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/components/types.ts +6 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/Markdown.tsx +71 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/MarkdownElement.tsx +81 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/elements/Blockquote/index.sass +6 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/elements/Blockquote/index.tsx +12 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/elements/Heading/index.sass +14 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/elements/Heading/index.tsx +17 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/elements/List/index.scss +16 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/elements/List/index.tsx +39 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/types/custom-types.d.ts +69 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/composables/useTextSelection.ts +50 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/index.sass +19 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/index.vue +21 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/shared/const.ts +23 -0
- package/src/web-components/smart-docx-editor/MarkdownShortcuts/utils/slateHelpers.ts +23 -0
- package/src/web-components/smart-docx-editor/data.ts +38 -0
- package/src/web-components/smart-docx-editor/demo.vue +11 -0
- package/src/web-components/smart-docx-editor/index.md +3 -0
- package/src/web-components/smart-docx-editor/index.ts +5 -0
- package/src/web-components/smart-docx-editor/index.vue +12 -0
- package/src/web-components/smart-docx-editor/info.ts +2 -0
- package/src/web-components/smart-file-preview/category/Code.vue +171 -0
- package/src/web-components/smart-file-preview/category/Image.vue +49 -0
- package/src/web-components/smart-file-preview/category/Pdf.vue +14 -0
- package/src/web-components/smart-file-preview/category/Video.vue +27 -0
- package/src/web-components/smart-file-preview/demo.vue +34 -0
- package/src/web-components/smart-file-preview/index.md +5 -0
- package/src/web-components/smart-file-preview/index.ts +29 -0
- package/src/web-components/smart-file-preview/index.vue +56 -0
- package/src/web-components/smart-file-preview/info.ts +2 -0
- package/src/web-components/smart-file-preview/shared/const.ts +4 -0
- package/src/web-components/smart-file-preview/types.ts +38 -0
- package/src/web-components/smart-upload/index.ts +5 -0
- package/src/web-components/smart-upload/index.vue +101 -0
- package/src/web-components/smart-upload/info.ts +2 -0
- package/src/web-components/smart-upload/types.ts +28 -0
- package/tsconfig.json +15 -0
- package/types/auto-imports.d.ts +975 -0
- package/types/components.d.ts +14 -0
- package/types/env.d.ts +8 -0
- package/types/shims.d.ts +6 -0
- package/unocss.config.ts +23 -0
- package/vite.config.ts +60 -0
@@ -0,0 +1,164 @@
|
|
1
|
+
<!-- 文件上传相关 -->
|
2
|
+
|
3
|
+
<template>
|
4
|
+
<div
|
5
|
+
v-if="showUploadPanel"
|
6
|
+
class="upload-panel w-100 max-w-[calc(100vw-48px)] max-h-[calc(100vh-48px)]"
|
7
|
+
flex="~ col" pos="fixed bottom-6 right-6" overflow-hidden bg-white z-6666
|
8
|
+
>
|
9
|
+
<!-- 标题区 -->
|
10
|
+
<div h-13.5 flex="~ items-center gap-1 none" transition-colors px-5.5>
|
11
|
+
<!-- 正在上传中 -->
|
12
|
+
<template v-if="uploadingFiles.length">
|
13
|
+
<i-line-md-uploading-loop class="size-6 c-primary mr-2" />
|
14
|
+
<span>正在上传 · 剩余 {{ unuploadedFiles.length + uploadingFiles.length }} 项</span>
|
15
|
+
</template>
|
16
|
+
<!-- 上传完成 -->
|
17
|
+
<template v-else>
|
18
|
+
<i-gridicons-checkmark-circle class="size-6 c-success mr-2" />
|
19
|
+
<span>上传完成</span>
|
20
|
+
<span v-if="fileList.length !== uploadSuccessFiles.length">· 失败 {{ fileList.length - uploadSuccessFiles.length }} 项</span>
|
21
|
+
<span>· 共 {{ fileList.length }} 项</span>
|
22
|
+
</template>
|
23
|
+
<div flex-grow />
|
24
|
+
<button class="upload-panel-btn" @click="showFileList = !showFileList">
|
25
|
+
<i-material-symbols-keyboard-arrow-down class="size-6.5 transition-all" :class="{ 'rotate-180': !showFileList }" />
|
26
|
+
</button>
|
27
|
+
<button class="upload-panel-btn" @click="showUploadPanel = false">
|
28
|
+
<i-material-symbols-close-rounded class="size-5.5" />
|
29
|
+
</button>
|
30
|
+
</div>
|
31
|
+
<!-- 上传文件列表 -->
|
32
|
+
<div v-auto-animate>
|
33
|
+
<el-scrollbar v-if="showFileList" height="480px">
|
34
|
+
<template v-for="file in fileList" :key="file.id">
|
35
|
+
<div :ref="(el) => file.ref = el" flex="~ items-center gap-3" relative px-5.5 py-3>
|
36
|
+
<!-- 图标 -->
|
37
|
+
<FileIcon :ext="file.fileExt" />
|
38
|
+
<!-- 文件信息 -->
|
39
|
+
<div flex="~ grow col" overflow-hidden>
|
40
|
+
<!-- 文件名 -->
|
41
|
+
<div font-mono text-sm flex>
|
42
|
+
<div truncate>{{ file.fileName }}</div>
|
43
|
+
<div v-if="file.fileExt">.{{ file.fileExt }}</div>
|
44
|
+
</div>
|
45
|
+
<!-- 已上传文件大小 / 文件大小 -->
|
46
|
+
<div font-mono text="xs neutral">
|
47
|
+
<span v-if="file.isUploading && isNumeric(file.progress)">{{ prettyBytes(file.file.size * file.progress / 100) }} /</span>
|
48
|
+
{{ prettyBytes(file.file.size) }}
|
49
|
+
<span v-if="file.isReading" inline-flex="~ items-center" gap-1>· 文件读取中<i-svg-spinners-bars-fade /></span>
|
50
|
+
<span v-else-if="file.isUploading" inline-flex="~ items-center" gap-1>· 文件上传中<i-svg-spinners-bars-fade /></span>
|
51
|
+
<template v-else-if="file.isUploadError">· <span c-red>上传失败</span></template>
|
52
|
+
<template v-else-if="file.isQuickUpload">· <span inline-flex="~ items-center" gap-1 c-success>极速秒传<i-fxemoji-rocket /></span></template>
|
53
|
+
<template v-else-if="file.isUploadSuccess">· <span c-success>上传成功</span></template>
|
54
|
+
</div>
|
55
|
+
</div>
|
56
|
+
<!-- 上传进度 -->
|
57
|
+
<div
|
58
|
+
h-full b-b="2 solid primary" pos="absolute top-0 left-0" bg-neutral-1 z--1 transition-width
|
59
|
+
v-bind="file.isUploadError ? { class: 'w-full b-red!' } : file.isUploading ? { style: { width: `${file.progress}%` } } : {}"
|
60
|
+
/>
|
61
|
+
</div>
|
62
|
+
</template>
|
63
|
+
</el-scrollbar>
|
64
|
+
</div>
|
65
|
+
</div>
|
66
|
+
</template>
|
67
|
+
|
68
|
+
<script lang="ts" setup>
|
69
|
+
import { isNumeric, uniqueKey } from 'mixte';
|
70
|
+
import { vAutoAnimate } from '@formkit/auto-animate/vue';
|
71
|
+
import prettyBytes from 'pretty-bytes';
|
72
|
+
import type { FileItem } from '../types';
|
73
|
+
import { uploadFile } from '../apis/file';
|
74
|
+
import FileIcon from './FileIcon.vue';
|
75
|
+
import { isFileEqual } from '@/utils/isFileEqual';
|
76
|
+
import { getFileNameAndExt } from '@/utils/getFileNameAndExt';
|
77
|
+
|
78
|
+
const emit = defineEmits(['success']);
|
79
|
+
|
80
|
+
/** 显示上传面板 */
|
81
|
+
const showUploadPanel = ref(false);
|
82
|
+
/** 显示文件列表 */
|
83
|
+
const showFileList = ref(true);
|
84
|
+
|
85
|
+
/** 文件上传线程 */
|
86
|
+
const uploadThreads = ref(
|
87
|
+
Array.from({ length: 5 }).map(() => {
|
88
|
+
const uploadThreadFile = ref<FileItem>();
|
89
|
+
return [uploadThreadFile, uploadFile(uploadThreadFile)] as const;
|
90
|
+
}),
|
91
|
+
);
|
92
|
+
|
93
|
+
/** 文件选择 */
|
94
|
+
const { open, onChange } = useFileDialog({ reset: true });
|
95
|
+
/** 文件夹选择 */
|
96
|
+
const { open: openDirectory, onChange: onChangeDirectory } = useFileDialog({ reset: true, directory: true });
|
97
|
+
/** 文件列表 */
|
98
|
+
const fileList = ref<FileItem[]>([]);
|
99
|
+
|
100
|
+
/** 正在上传的文件列表 */
|
101
|
+
const uploadingFiles = computed(() => fileList.value.filter(item => item.isReading || item.isUploading));
|
102
|
+
/** 上传成功的文件列表 */
|
103
|
+
const uploadSuccessFiles = computed(() => fileList.value.filter(item => item.isUploadSuccess));
|
104
|
+
/** 未上传的文件列表 */
|
105
|
+
const unuploadedFiles = computed(() => fileList.value.filter(item => !item.isReading && !item.isUploading && !item.isUploadSuccess && !item.isUploadError));
|
106
|
+
/** 空余线程列表 */
|
107
|
+
const freeThreads = computed(() => uploadThreads.value.filter(([item]) => !item.value || !item.value.isUploading));
|
108
|
+
|
109
|
+
// 选择 文件/文件夹 的回调
|
110
|
+
[onChange, onChangeDirectory].forEach(fn => fn((files: FileList) => {
|
111
|
+
if (!files?.length) return;
|
112
|
+
if (fileList.value.length === uploadSuccessFiles.value.length) fileList.value = [];
|
113
|
+
|
114
|
+
showUploadPanel.value = true;
|
115
|
+
showFileList.value = true;
|
116
|
+
|
117
|
+
Array.from(files).forEach((file) => {
|
118
|
+
if (unuploadedFiles.value.find(item => isFileEqual(item.file, file))) return;
|
119
|
+
|
120
|
+
const { name, ext } = getFileNameAndExt(file.name);
|
121
|
+
|
122
|
+
fileList.value.push({
|
123
|
+
id: uniqueKey(fileList.value),
|
124
|
+
file,
|
125
|
+
fileName: name,
|
126
|
+
fileExt: ext,
|
127
|
+
});
|
128
|
+
});
|
129
|
+
}));
|
130
|
+
|
131
|
+
// 有未上传的文件和空余线程时, 开始上传
|
132
|
+
wheneverEffectScopeImmediate(() => unuploadedFiles.value.length > 0 && freeThreads.value.length > 0, () => {
|
133
|
+
freeThreads.value.forEach((freeThread) => {
|
134
|
+
if (!unuploadedFiles.value[0]) return;
|
135
|
+
freeThread[0].value = unuploadedFiles.value[0];
|
136
|
+
freeThread[1].execute();
|
137
|
+
});
|
138
|
+
});
|
139
|
+
// 滚动滚动条至正在上传的文件
|
140
|
+
wheneverImmediate(() => uploadingFiles.value[0], (uploadingFile) => {
|
141
|
+
(uploadingFile.ref as HTMLDivElement)?.scrollIntoView({ block: 'center', inline: 'center' });
|
142
|
+
});
|
143
|
+
// 上传完成, 且没有未上传的文件, 且有上传成功的文件时, 通知父组件 ( 上传失败的暂时不管 )
|
144
|
+
wheneverEffectScopeImmediate(() => fileList.value.length, () => {
|
145
|
+
wheneverImmediate(() => !uploadingFiles.value.length && !unuploadedFiles.value.length && !!uploadSuccessFiles.value.length, () => {
|
146
|
+
emit('success');
|
147
|
+
});
|
148
|
+
});
|
149
|
+
|
150
|
+
defineExpose({
|
151
|
+
open,
|
152
|
+
openDirectory,
|
153
|
+
});
|
154
|
+
</script>
|
155
|
+
|
156
|
+
<style lang="sass" scoped>
|
157
|
+
.upload-panel
|
158
|
+
box-shadow: 0 0 1px 1px rgba(28,28,32,.05), 0 8px 24px rgba(28,28,32,.12)
|
159
|
+
border-radius: 5px
|
160
|
+
.upload-panel-btn
|
161
|
+
@apply size-7 flex-(~ justify-center items-center)
|
162
|
+
@apply c-neutral bg-transparent
|
163
|
+
@apply hover:c-neutral-5 hover:bg-neutral-1
|
164
|
+
</style>
|
@@ -0,0 +1,62 @@
|
|
1
|
+
<!-- 文件图标 -->
|
2
|
+
|
3
|
+
<template>
|
4
|
+
<div class="size-9 text-9 relative overflow-hidden [&>svg]-absolute" flex="~ justify-center items-center none">
|
5
|
+
<!-- 代码相关 -->
|
6
|
+
<i-bi-filetype-js v-if="ext === 'js'" />
|
7
|
+
<i-ph-file-ts-light v-else-if="ext === 'ts'" style="font-size: 117%" />
|
8
|
+
<i-bi-filetype-jsx v-else-if="ext === 'jsx'" />
|
9
|
+
<i-bi-filetype-tsx v-else-if="ext === 'tsx'" />
|
10
|
+
<i-bi-filetype-xml v-else-if="ext === 'xml'" />
|
11
|
+
<i-bi-filetype-html v-else-if="ext === 'html'" />
|
12
|
+
<i-bi-filetype-css v-else-if="ext === 'css'" />
|
13
|
+
<i-bi-filetype-sass v-else-if="ext === 'sass'" />
|
14
|
+
<i-bi-filetype-scss v-else-if="ext === 'scss'" />
|
15
|
+
<!-- less -->
|
16
|
+
<i-bi-filetype-yml v-else-if="['yml', 'yaml'].includes(ext)" />
|
17
|
+
<i-bi-filetype-json v-else-if="ext === 'json'" />
|
18
|
+
<i-bi-filetype-txt v-else-if="ext === 'txt'" />
|
19
|
+
<i-bi-filetype-md v-else-if="ext === 'md'" />
|
20
|
+
<i-bi-filetype-mdx v-else-if="ext === 'mdx'" />
|
21
|
+
<i-vscode-icons-file-type-vue v-else-if="ext === 'vue'" />
|
22
|
+
<i-bi-filetype-java v-else-if="ext === 'java'" />
|
23
|
+
<i-bi-filetype-php v-else-if="ext === 'php'" />
|
24
|
+
<i-bi-filetype-sql v-else-if="ext === 'sql'" />
|
25
|
+
<i-vscode-icons-file-type-config v-else-if="ext === 'properties'" />
|
26
|
+
<i-vscode-icons-file-type-bat v-else-if="ext === 'bat'" />
|
27
|
+
<i-vscode-icons-file-type-shell v-else-if="ext === 'sh'" />
|
28
|
+
<i-vscode-icons-file-type-npm v-else-if="ext === 'npmrc'" />
|
29
|
+
<i-vscode-icons-file-type-git v-else-if="ext === 'gitignore'" class="grayscale-100" />
|
30
|
+
<i-vscode-icons-file-type-eslint v-else-if="ext === 'eslintrc'" />
|
31
|
+
<i-vscode-icons-file-type-eslint v-else-if="ext === 'eslintignore'" class="grayscale-100" />
|
32
|
+
<!-- 图片 -->
|
33
|
+
<i-bi-filetype-jpg v-else-if="['jpg', 'jpeg'].includes(ext)" />
|
34
|
+
<i-bi-filetype-png v-else-if="ext === 'png'" />
|
35
|
+
<i-bi-filetype-bmp v-else-if="ext === 'bmp'" />
|
36
|
+
<i-bi-filetype-gif v-else-if="ext === 'gif'" />
|
37
|
+
<i-bi-filetype-svg v-else-if="ext === 'svg'" />
|
38
|
+
<!-- webp -->
|
39
|
+
<!-- PDF -->
|
40
|
+
<i-bi-filetype-pdf v-else-if="ext === 'pdf'" />
|
41
|
+
|
42
|
+
<i-bi-filetype-ppt v-else-if="ext === 'ppt'" />
|
43
|
+
<i-bi-filetype-pptx v-else-if="ext === 'pptx'" />
|
44
|
+
<i-bi-filetype-xls v-else-if="ext === 'xls'" />
|
45
|
+
<i-bi-filetype-xlsx v-else-if="ext === 'xlsx'" />
|
46
|
+
<i-bi-filetype-doc v-else-if="ext === 'doc'" />
|
47
|
+
<i-bi-filetype-docx v-else-if="ext === 'docx'" />
|
48
|
+
<i-bi-file-earmark-zip v-else-if="['zip', '7z', 'rar'].includes(ext)" />
|
49
|
+
|
50
|
+
<i-bi-filetype-psd v-else-if="ext === 'psd'" />
|
51
|
+
<i-bi-filetype-raw v-else-if="ext === 'raw'" />
|
52
|
+
<i-bi-filetype-exe v-else-if="ext === 'exe'" />
|
53
|
+
<i-bi-filetype-mp3 v-else-if="ext === 'mp3'" />
|
54
|
+
<i-bi-filetype-mp4 v-else-if="ext === 'mp4'" />
|
55
|
+
<i-formkit-filedoc v-else />
|
56
|
+
</div>
|
57
|
+
</template>
|
58
|
+
|
59
|
+
<!-- bat, sh, -->
|
60
|
+
<script lang="ts" setup>
|
61
|
+
defineProps<{ ext: string }>();
|
62
|
+
</script>
|
@@ -0,0 +1,65 @@
|
|
1
|
+
<template>
|
2
|
+
<div flex="~ justify-center items-center none gap-2" v-bind="attrs">
|
3
|
+
<slot v-if="slots.title" name="title" />
|
4
|
+
<b v-else text-xl>{{ title }}</b>
|
5
|
+
|
6
|
+
<div flex-grow />
|
7
|
+
|
8
|
+
<div flex="~ gap-2">
|
9
|
+
<CustomPopover placement="bottom-start">
|
10
|
+
<template #reference>
|
11
|
+
<el-button class="px-5" :color="colors.blue[6]" plain>上传<i-ic-outline-keyboard-arrow-down class="size-5 ml-1 -mr-2" /></el-button>
|
12
|
+
</template>
|
13
|
+
|
14
|
+
<div class="cp-item" @click="uploadRef!.open()"><i-material-symbols-upload-file class="size-6 c-orange" />上传文件</div>
|
15
|
+
<div class="cp-item" @click="uploadRef!.openDirectory()"><i-ph-folder-notch-plus-fill class="size-6 c-yellow" />上传文件夹</div>
|
16
|
+
</CustomPopover>
|
17
|
+
|
18
|
+
<CustomPopover placement="bottom-end">
|
19
|
+
<template #reference>
|
20
|
+
<el-button class="px-5 ml-0!" :color="colors.blue[6]">新建<i-ic-outline-keyboard-arrow-down class="size-5 ml-1 -mr-2" /></el-button>
|
21
|
+
</template>
|
22
|
+
|
23
|
+
<div class="cp-item" @click="createDocRef?.createDoc()"><i-ri-file-text-fill class="size-6 c-blue-5" />文档</div>
|
24
|
+
<div class="cp-item" @click="toCreateDir()"><i-ph-folder-fill class="size-6 c-yellow" />文件夹</div>
|
25
|
+
</CustomPopover>
|
26
|
+
</div>
|
27
|
+
</div>
|
28
|
+
|
29
|
+
<!-- 文件上传相关 -->
|
30
|
+
<DocxUpload ref="uploadRef" @success="emit('upload-success')" />
|
31
|
+
<!-- 云文档 -->
|
32
|
+
<DocxDoc ref="createDocRef" />
|
33
|
+
</template>
|
34
|
+
|
35
|
+
<script lang="ts" setup>
|
36
|
+
import CustomPopover from '../components/CustomPopover.vue';
|
37
|
+
import DocxUpload from '../components/DocxUpload.vue';
|
38
|
+
import DocxDoc from '../components/DocxDoc.vue';
|
39
|
+
import { createDir } from '../apis/folder';
|
40
|
+
import { usePrompt } from '../composables/usePrompt';
|
41
|
+
import { colors } from '@/shared/unocss.theme';
|
42
|
+
|
43
|
+
interface Props {
|
44
|
+
title?: string
|
45
|
+
}
|
46
|
+
|
47
|
+
defineProps<Props>();
|
48
|
+
|
49
|
+
const emit = defineEmits(['upload-success']);
|
50
|
+
const slots = defineSlots<{
|
51
|
+
title?: () => any
|
52
|
+
}>();
|
53
|
+
|
54
|
+
const attrs = useAttrs();
|
55
|
+
|
56
|
+
const uploadRef = ref<typeof DocxUpload>();
|
57
|
+
const createDocRef = ref<typeof DocxDoc>();
|
58
|
+
|
59
|
+
function toCreateDir() {
|
60
|
+
usePrompt('', '新建文件夹', async (instance) => {
|
61
|
+
await createDir({ fileName: instance.inputValue }).execute();
|
62
|
+
emit('upload-success');
|
63
|
+
});
|
64
|
+
}
|
65
|
+
</script>
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<template>
|
2
|
+
<div flex="~ items-center gap-2">
|
3
|
+
<div class="[&>*]-size-10">
|
4
|
+
<img v-if="icon" :src="icon">
|
5
|
+
<i-system-uicons-document-stack v-else />
|
6
|
+
</div>
|
7
|
+
<b text-sm>{{ title || 'SmartOS 云文档' }}</b>
|
8
|
+
</div>
|
9
|
+
</template>
|
10
|
+
|
11
|
+
<script lang="ts" setup>
|
12
|
+
import type { SmartDocxDrivePageProps } from '../types';
|
13
|
+
|
14
|
+
defineProps<Pick<SmartDocxDrivePageProps, 'icon' | 'title'>>();
|
15
|
+
</script>
|
@@ -0,0 +1,34 @@
|
|
1
|
+
<template>
|
2
|
+
<div flex="~ col gap-1">
|
3
|
+
<!-- 主页 -->
|
4
|
+
<DocxDir
|
5
|
+
:class="{ active: activeMenu === 'home' }" :showOpen="false" name="主页" hiddenActions
|
6
|
+
@click="activeMenu = 'home'"
|
7
|
+
>
|
8
|
+
<template #icon>
|
9
|
+
<i-ic-baseline-home class="size-5.5" />
|
10
|
+
</template>
|
11
|
+
</DocxDir>
|
12
|
+
<!-- 我的空间 -->
|
13
|
+
<DocxDir
|
14
|
+
:class="{ active: activeMenu === 'me' && (dirTree.isLoading || meActiveMenu === dirTree.data?.data.id) }"
|
15
|
+
:info="dirTree.data?.data" name="我的空间" hiddenActions
|
16
|
+
@click="activeMenu = 'me'"
|
17
|
+
>
|
18
|
+
<template #icon="{ isActive }">
|
19
|
+
<i-ph-folder-minus-fill v-if="isActive" class="size-5" />
|
20
|
+
<i-ph-folder-minus-duotone v-else class="size-5" />
|
21
|
+
</template>
|
22
|
+
</DocxDir>
|
23
|
+
</div>
|
24
|
+
</template>
|
25
|
+
|
26
|
+
<script lang="tsx" setup>
|
27
|
+
import { menuStore } from '../stores/menu';
|
28
|
+
import DocxDir from '../components/DocxDir.vue';
|
29
|
+
|
30
|
+
const {
|
31
|
+
activeMenu, meActiveMenu,
|
32
|
+
dirTree,
|
33
|
+
} = menuStore();
|
34
|
+
</script>
|
@@ -0,0 +1,36 @@
|
|
1
|
+
<!-- 顶部固定内容 -->
|
2
|
+
|
3
|
+
<template>
|
4
|
+
<div flex="~ items-center none">
|
5
|
+
<div flex-grow>
|
6
|
+
<el-input placeholder="搜索">
|
7
|
+
<template #prefix>
|
8
|
+
<i-ph-magnifying-glass class="size-4.5 c-black" />
|
9
|
+
</template>
|
10
|
+
</el-input>
|
11
|
+
</div>
|
12
|
+
|
13
|
+
<div flex-grow />
|
14
|
+
|
15
|
+
<!-- <el-avatar class="cursor-pointer" :size="36" :src="info.data?.data?.sysUser?.avatar">
|
16
|
+
<i-svg-spinners-3-dots-scale v-if="info.isLoading" class="size-4" />
|
17
|
+
<i-tdesign-user-error-1 v-else-if="info.data?.data?.sysUser?.avatar" class="size-5 relative left-.3" />
|
18
|
+
<i-material-symbols-person v-else class="size-5" />
|
19
|
+
</el-avatar> -->
|
20
|
+
</div>
|
21
|
+
</template>
|
22
|
+
|
23
|
+
<script lang="ts" setup>
|
24
|
+
// import { getConfigProvider } from '@smartos-lib/core';
|
25
|
+
|
26
|
+
// const axios = getConfigProvider('request')!;
|
27
|
+
|
28
|
+
// const info = useRequestReactive<any, any>(() => {
|
29
|
+
// return axios({ url: '/admin/user/info', method: 'get' });
|
30
|
+
// });
|
31
|
+
|
32
|
+
// info.execute();
|
33
|
+
// info.onSuccess(() => {
|
34
|
+
// console.log(info.data); // eslint-disable-line no-console
|
35
|
+
// });
|
36
|
+
</script>
|
@@ -0,0 +1,41 @@
|
|
1
|
+
import type { ElDialog } from 'element-plus';
|
2
|
+
|
3
|
+
export function useFullscreenElDialog(dialogRef: Ref<typeof ElDialog | undefined>) {
|
4
|
+
wheneverEffectScopeImmediate(() => dialogRef.value?.dialogContentRef, (dialog) => {
|
5
|
+
const dialogElement = unrefElement(dialog) as HTMLDivElement | null;
|
6
|
+
const header = dialogElement?.querySelector<HTMLDivElement>('.smart-dialog__header');
|
7
|
+
const headerClose = dialogElement?.querySelector<HTMLButtonElement>('.smart-dialog__headerbtn');
|
8
|
+
const body = dialogElement?.querySelector<HTMLDivElement>('.smart-dialog__body');
|
9
|
+
|
10
|
+
dialogElement && Object.assign(dialogElement.style, {
|
11
|
+
'width': '100%',
|
12
|
+
'height': '100%',
|
13
|
+
'display': 'flex',
|
14
|
+
'flex-direction': 'column',
|
15
|
+
'border-radius': 0,
|
16
|
+
'margin': 0,
|
17
|
+
});
|
18
|
+
|
19
|
+
header && Object.assign(header.style, {
|
20
|
+
'display': 'flex',
|
21
|
+
'align-items': 'center',
|
22
|
+
'flex': 'none',
|
23
|
+
'height': '54px',
|
24
|
+
'padding': '0 20px',
|
25
|
+
});
|
26
|
+
|
27
|
+
headerClose && Object.assign(headerClose.style, {
|
28
|
+
'display': 'flex',
|
29
|
+
'justify-content': 'center',
|
30
|
+
'align-items': 'center',
|
31
|
+
'top': '0px',
|
32
|
+
});
|
33
|
+
|
34
|
+
body && Object.assign(body.style, {
|
35
|
+
'flex-grow': 1,
|
36
|
+
'padding': 0,
|
37
|
+
'overflow': 'hidden',
|
38
|
+
'border-top': '1px solid #ebeef5',
|
39
|
+
});
|
40
|
+
});
|
41
|
+
}
|
@@ -0,0 +1,73 @@
|
|
1
|
+
import type { ElMessageBoxOptions, MessageBoxData, MessageBoxState } from 'element-plus';
|
2
|
+
import type { Awaitable } from '@vueuse/core';
|
3
|
+
import { ElMessageBox } from 'element-plus';
|
4
|
+
import { isFunction, isPlainObject } from 'mixte';
|
5
|
+
|
6
|
+
let type: 'confirm' | 'prompt' = 'prompt';
|
7
|
+
|
8
|
+
type UseConfirmFn = (instance: MessageBoxState, done: () => any) => Awaitable<boolean | void>;
|
9
|
+
|
10
|
+
export function usePrompt(message: ElMessageBoxOptions['message'], title: ElMessageBoxOptions['title'], fn: UseConfirmFn, options?: ElMessageBoxOptions): Promise<MessageBoxData>;
|
11
|
+
export function usePrompt(message: ElMessageBoxOptions['message'], title: ElMessageBoxOptions['title'], options: ElMessageBoxOptions, fn: UseConfirmFn): Promise<MessageBoxData>;
|
12
|
+
|
13
|
+
export function usePrompt(
|
14
|
+
message: ElMessageBoxOptions['message'],
|
15
|
+
title: ElMessageBoxOptions['title'],
|
16
|
+
arg1: any,
|
17
|
+
arg2?: any,
|
18
|
+
) {
|
19
|
+
let userConfirmFn: UseConfirmFn = arg1;
|
20
|
+
let userOptions: ElMessageBoxOptions = arg2;
|
21
|
+
|
22
|
+
if (!isFunction(arg1)) {
|
23
|
+
userConfirmFn = arg2;
|
24
|
+
userOptions = arg1;
|
25
|
+
}
|
26
|
+
|
27
|
+
const options: ElMessageBoxOptions = {
|
28
|
+
closeOnClickModal: false, // 默认不可通过点击遮罩关闭
|
29
|
+
closeOnPressEscape: false, // 默认不可通过按下 ESC 键关闭
|
30
|
+
};
|
31
|
+
|
32
|
+
if (isPlainObject(userOptions))
|
33
|
+
Object.assign(options, userOptions, { beforeClose: undefined });
|
34
|
+
|
35
|
+
if (isFunction(userConfirmFn)) {
|
36
|
+
options.beforeClose = async function (action, instance, done) {
|
37
|
+
if (action !== 'confirm') return done();
|
38
|
+
|
39
|
+
const originConfirmButtonText = instance.confirmButtonText;
|
40
|
+
|
41
|
+
instance.confirmButtonLoading = true;
|
42
|
+
instance.confirmButtonText = '执行中...';
|
43
|
+
|
44
|
+
try {
|
45
|
+
if (await userConfirmFn(instance, done) !== false) done();
|
46
|
+
}
|
47
|
+
catch (error) {
|
48
|
+
console.error(error);
|
49
|
+
}
|
50
|
+
finally {
|
51
|
+
instance.confirmButtonLoading = false;
|
52
|
+
instance.confirmButtonText = originConfirmButtonText;
|
53
|
+
}
|
54
|
+
};
|
55
|
+
}
|
56
|
+
|
57
|
+
return ElMessageBox[type](message, title, options);
|
58
|
+
}
|
59
|
+
|
60
|
+
export function useConfirm(message: ElMessageBoxOptions['message'], title: ElMessageBoxOptions['title'], fn: UseConfirmFn, options?: ElMessageBoxOptions): Promise<MessageBoxData>;
|
61
|
+
export function useConfirm(message: ElMessageBoxOptions['message'], title: ElMessageBoxOptions['title'], options: ElMessageBoxOptions, fn: UseConfirmFn): Promise<MessageBoxData>;
|
62
|
+
|
63
|
+
export function useConfirm(
|
64
|
+
message: ElMessageBoxOptions['message'],
|
65
|
+
title: ElMessageBoxOptions['title'],
|
66
|
+
arg1: any,
|
67
|
+
arg2?: any,
|
68
|
+
) {
|
69
|
+
type = 'confirm';
|
70
|
+
const result = usePrompt(message, title, arg1, arg2);
|
71
|
+
type = 'prompt';
|
72
|
+
return result;
|
73
|
+
}
|
@@ -0,0 +1,10 @@
|
|
1
|
+
import type { SmartDocxDrivePageProps } from './types';
|
2
|
+
|
3
|
+
export const 换图标和名称: SmartDocxDrivePageProps = {
|
4
|
+
icon: 'https://starter-template-base.moomfe.com/favicon.svg',
|
5
|
+
title: 'Mixte',
|
6
|
+
};
|
7
|
+
|
8
|
+
export const 激活第二菜单: SmartDocxDrivePageProps = {
|
9
|
+
activeMenu: 'me',
|
10
|
+
};
|
@@ -0,0 +1 @@
|
|
1
|
+
@import './custom-popover.sass'
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import App from './App.vue';
|
2
|
+
import ExternalStyle from './external-style/index.sass?inline';
|
3
|
+
import { tag } from './info';
|
4
|
+
import { defineCustomElements } from '@/utils/defineCustomElements';
|
5
|
+
|
6
|
+
defineCustomElements(tag, App);
|
7
|
+
|
8
|
+
// 插入外置样式
|
9
|
+
{
|
10
|
+
const style = document.createElement('style');
|
11
|
+
style.setAttribute('type', 'text/css');
|
12
|
+
style.setAttribute('data-component', tag);
|
13
|
+
style.textContent = ExternalStyle;
|
14
|
+
|
15
|
+
document.querySelectorAll(`[data-component=${tag}]`).forEach((style) => {
|
16
|
+
style.parentNode?.removeChild(style);
|
17
|
+
});
|
18
|
+
|
19
|
+
document.head.appendChild(style);
|
20
|
+
}
|
@@ -0,0 +1,39 @@
|
|
1
|
+
<!--
|
2
|
+
页面主框架
|
3
|
+
-->
|
4
|
+
|
5
|
+
<template>
|
6
|
+
<div root size-full relative>
|
7
|
+
<div size-full pos="absolute top-0 left-0" grid="~ cols-[260px_auto]">
|
8
|
+
<!-- 侧边栏 -->
|
9
|
+
<div size-full grid="~ rows-[auto_1fr]" bg-neutral-1 pl2>
|
10
|
+
<Logo py-4 pl-2.5 :icon="icon" :title="title" />
|
11
|
+
<div size-full relative>
|
12
|
+
<div size-full pos="absolute top-0 left-0" scrollbar pr2>
|
13
|
+
<Menu />
|
14
|
+
</div>
|
15
|
+
</div>
|
16
|
+
</div>
|
17
|
+
<!-- 右侧内容 -->
|
18
|
+
<div size-full flex="~ col" overflow-hidden p="l7 r4 t4">
|
19
|
+
<Navbar class="mb6" />
|
20
|
+
<Home v-if="activeMenu === 'home'" />
|
21
|
+
<Me v-else-if="activeMenu === 'me'" />
|
22
|
+
</div>
|
23
|
+
</div>
|
24
|
+
</div>
|
25
|
+
</template>
|
26
|
+
|
27
|
+
<script lang="ts" setup>
|
28
|
+
import Logo from './components-private/Logo.vue';
|
29
|
+
import Menu from './components-private/Menu.vue';
|
30
|
+
import Navbar from './components-private/Navbar.vue';
|
31
|
+
import Home from './children/Home.vue';
|
32
|
+
import Me from './children/Me.vue';
|
33
|
+
import type { SmartDocxDrivePageProps } from './types';
|
34
|
+
import { menuStore } from './stores/menu';
|
35
|
+
|
36
|
+
defineProps<SmartDocxDrivePageProps>();
|
37
|
+
|
38
|
+
const { activeMenu } = menuStore();
|
39
|
+
</script>
|
@@ -0,0 +1,60 @@
|
|
1
|
+
import type { DirInfo } from '../apis/folder';
|
2
|
+
import { getDirTree } from '../apis/folder';
|
3
|
+
import type { SmartDocxDrivePageProps } from '../types';
|
4
|
+
|
5
|
+
/**
|
6
|
+
* 菜单相关
|
7
|
+
*/
|
8
|
+
export const menuStore = createGlobalState(() => {
|
9
|
+
/** 当前激活的菜单项 */
|
10
|
+
const activeMenu = ref<SmartDocxDrivePageProps['activeMenu']>();
|
11
|
+
/** 我的空间打开的文件夹 */
|
12
|
+
const meActiveMenu = ref('');
|
13
|
+
|
14
|
+
/** 我的空间 / 目录树请求相关 */
|
15
|
+
const dirTree = getDirTree({ immediate: true });
|
16
|
+
/** 我的空间 / 平铺的目录树 */
|
17
|
+
const flatDirTree = computed(() => {
|
18
|
+
if (!dirTree.data?.data) return [];
|
19
|
+
return [dirTree.data.data].flatMap(function map(item): DirInfo[] {
|
20
|
+
return [item, ...item.children.flatMap(map)];
|
21
|
+
});
|
22
|
+
});
|
23
|
+
|
24
|
+
/** 我的空间 / 打开的文件夹信息 */
|
25
|
+
const meActiveMenuInfo = computed(() => {
|
26
|
+
return flatDirTree.value.find(item => item.id === meActiveMenu.value);
|
27
|
+
});
|
28
|
+
|
29
|
+
/** 我的空间 / 打开的文件夹层级信息 */
|
30
|
+
const meActiveMenuPathInfo = computed(() => {
|
31
|
+
const pathInfo: DirInfo[] = [];
|
32
|
+
let parent = meActiveMenuInfo.value;
|
33
|
+
|
34
|
+
while (parent) {
|
35
|
+
pathInfo.unshift(parent);
|
36
|
+
parent = flatDirTree.value.find(({ children }) => children.find(item => item.id === parent!.id))!;
|
37
|
+
}
|
38
|
+
|
39
|
+
return pathInfo;
|
40
|
+
});
|
41
|
+
|
42
|
+
const { off } = dirTree.onSuccess(() => {
|
43
|
+
off();
|
44
|
+
meActiveMenu.value = dirTree.data?.data.id ?? '';
|
45
|
+
});
|
46
|
+
|
47
|
+
whenever(() => activeMenu.value !== 'me', () => {
|
48
|
+
meActiveMenu.value = '';
|
49
|
+
});
|
50
|
+
|
51
|
+
return {
|
52
|
+
activeMenu,
|
53
|
+
meActiveMenu,
|
54
|
+
|
55
|
+
dirTree,
|
56
|
+
flatDirTree,
|
57
|
+
meActiveMenuInfo,
|
58
|
+
meActiveMenuPathInfo,
|
59
|
+
};
|
60
|
+
});
|