@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.
Files changed (128) hide show
  1. package/.eslintrc +12 -0
  2. package/.eslintrc-auto-import.json +332 -0
  3. package/Components.code-workspace +143 -0
  4. package/LICENSE +21 -0
  5. package/dist/smart-docx-editor/index.d.ts +2 -0
  6. package/dist/smart-docx-editor/index.js +68 -0
  7. package/dist/smart-file-preview/index.d.ts +18 -0
  8. package/dist/smart-file-preview/index.js +37 -0
  9. package/dist/smart-upload/index.d.ts +2 -0
  10. package/dist/smart-upload/index.js +800 -0
  11. package/index.html +16 -0
  12. package/package.json +23 -0
  13. package/public/favicon.svg +6 -0
  14. package/scripts/components.vite.config.ts +96 -0
  15. package/scripts/shared.ts +9 -0
  16. package/src/App.vue +28 -0
  17. package/src/components/Logo/index.vue +15 -0
  18. package/src/components-private/.gitkeep +0 -0
  19. package/src/composables/useElementStyle.ts +23 -0
  20. package/src/composables/useNaiveStyle.ts +43 -0
  21. package/src/composables/useNaiveTheme.ts +71 -0
  22. package/src/composables/useSmart.ts +36 -0
  23. package/src/layouts/default.vue +3 -0
  24. package/src/main.ts +33 -0
  25. package/src/modules/pinia/index.ts +8 -0
  26. package/src/modules/progress/index.ts +12 -0
  27. package/src/modules/router/install.ts +9 -0
  28. package/src/modules/router/routes.ts +40 -0
  29. package/src/pages/[...all].vue +21 -0
  30. package/src/pages/frame/component/[name].vue +14 -0
  31. package/src/pages/frame/index.vue +81 -0
  32. package/src/pages/index/composables/useTabsManage.ts +46 -0
  33. package/src/pages/index/index.vue +111 -0
  34. package/src/pages/index/type.ts +13 -0
  35. package/src/pages/index/utils/index.ts +41 -0
  36. package/src/settings.ts +9 -0
  37. package/src/shared/components.ts +52 -0
  38. package/src/shared/env.ts +11 -0
  39. package/src/shared/unocss.theme.ts +1600 -0
  40. package/src/stores/theme.ts +29 -0
  41. package/src/styles/element.scss +3 -0
  42. package/src/styles/styles.scss +21 -0
  43. package/src/types.ts +20 -0
  44. package/src/utils/callCustomElementExposed.ts +6 -0
  45. package/src/utils/deepCloneESModule.ts +10 -0
  46. package/src/utils/defineCustomElements.ts +18 -0
  47. package/src/utils/formatComponentsGlob.ts +16 -0
  48. package/src/utils/getFileMD5.ts +31 -0
  49. package/src/utils/getFileNameAndExt.ts +11 -0
  50. package/src/utils/isFileEqual.ts +13 -0
  51. package/src/utils/jsonToFormData.ts +8 -0
  52. package/src/web-components/smart-docx-drive-page/App.vue +37 -0
  53. package/src/web-components/smart-docx-drive-page/apis/doc.ts +85 -0
  54. package/src/web-components/smart-docx-drive-page/apis/file.ts +278 -0
  55. package/src/web-components/smart-docx-drive-page/apis/folder.ts +72 -0
  56. package/src/web-components/smart-docx-drive-page/children/Home.vue +8 -0
  57. package/src/web-components/smart-docx-drive-page/children/Me.vue +47 -0
  58. package/src/web-components/smart-docx-drive-page/components/CustomImage.vue +26 -0
  59. package/src/web-components/smart-docx-drive-page/components/CustomPopover.vue +62 -0
  60. package/src/web-components/smart-docx-drive-page/components/DocxDir.vue +99 -0
  61. package/src/web-components/smart-docx-drive-page/components/DocxDoc.vue +132 -0
  62. package/src/web-components/smart-docx-drive-page/components/DocxDownloadPopoverItem.vue +41 -0
  63. package/src/web-components/smart-docx-drive-page/components/DocxFileList.vue +156 -0
  64. package/src/web-components/smart-docx-drive-page/components/DocxPreview.vue +33 -0
  65. package/src/web-components/smart-docx-drive-page/components/DocxUpload.vue +164 -0
  66. package/src/web-components/smart-docx-drive-page/components/FileIcon.vue +62 -0
  67. package/src/web-components/smart-docx-drive-page/components-private/Header.vue +65 -0
  68. package/src/web-components/smart-docx-drive-page/components-private/Logo.vue +15 -0
  69. package/src/web-components/smart-docx-drive-page/components-private/Menu.vue +34 -0
  70. package/src/web-components/smart-docx-drive-page/components-private/Navbar.vue +36 -0
  71. package/src/web-components/smart-docx-drive-page/composables/useFullscreenElDialog.ts +41 -0
  72. package/src/web-components/smart-docx-drive-page/composables/usePrompt.ts +73 -0
  73. package/src/web-components/smart-docx-drive-page/data.ts +10 -0
  74. package/src/web-components/smart-docx-drive-page/external-style/custom-popover.sass +8 -0
  75. package/src/web-components/smart-docx-drive-page/external-style/index.sass +1 -0
  76. package/src/web-components/smart-docx-drive-page/index.ts +20 -0
  77. package/src/web-components/smart-docx-drive-page/index.vue +39 -0
  78. package/src/web-components/smart-docx-drive-page/info.ts +2 -0
  79. package/src/web-components/smart-docx-drive-page/stores/menu.ts +60 -0
  80. package/src/web-components/smart-docx-drive-page/types.ts +51 -0
  81. package/src/web-components/smart-docx-drive-page/utils/file-actions.ts +63 -0
  82. package/src/web-components/smart-docx-drive-page/utils/file.ts +31 -0
  83. package/src/web-components/smart-docx-editor/App.vue +32 -0
  84. package/src/web-components/smart-docx-editor/MarkdownShortcuts/components/Markdown.vue +202 -0
  85. package/src/web-components/smart-docx-editor/MarkdownShortcuts/components/Menu.vue +100 -0
  86. package/src/web-components/smart-docx-editor/MarkdownShortcuts/components/types.ts +6 -0
  87. package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/Markdown.tsx +71 -0
  88. package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/MarkdownElement.tsx +81 -0
  89. package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/elements/Blockquote/index.sass +6 -0
  90. package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/elements/Blockquote/index.tsx +12 -0
  91. package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/elements/Heading/index.sass +14 -0
  92. package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/elements/Heading/index.tsx +17 -0
  93. package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/elements/List/index.scss +16 -0
  94. package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/elements/List/index.tsx +39 -0
  95. package/src/web-components/smart-docx-editor/MarkdownShortcuts/components-react/types/custom-types.d.ts +69 -0
  96. package/src/web-components/smart-docx-editor/MarkdownShortcuts/composables/useTextSelection.ts +50 -0
  97. package/src/web-components/smart-docx-editor/MarkdownShortcuts/index.sass +19 -0
  98. package/src/web-components/smart-docx-editor/MarkdownShortcuts/index.vue +21 -0
  99. package/src/web-components/smart-docx-editor/MarkdownShortcuts/shared/const.ts +23 -0
  100. package/src/web-components/smart-docx-editor/MarkdownShortcuts/utils/slateHelpers.ts +23 -0
  101. package/src/web-components/smart-docx-editor/data.ts +38 -0
  102. package/src/web-components/smart-docx-editor/demo.vue +11 -0
  103. package/src/web-components/smart-docx-editor/index.md +3 -0
  104. package/src/web-components/smart-docx-editor/index.ts +5 -0
  105. package/src/web-components/smart-docx-editor/index.vue +12 -0
  106. package/src/web-components/smart-docx-editor/info.ts +2 -0
  107. package/src/web-components/smart-file-preview/category/Code.vue +171 -0
  108. package/src/web-components/smart-file-preview/category/Image.vue +49 -0
  109. package/src/web-components/smart-file-preview/category/Pdf.vue +14 -0
  110. package/src/web-components/smart-file-preview/category/Video.vue +27 -0
  111. package/src/web-components/smart-file-preview/demo.vue +34 -0
  112. package/src/web-components/smart-file-preview/index.md +5 -0
  113. package/src/web-components/smart-file-preview/index.ts +29 -0
  114. package/src/web-components/smart-file-preview/index.vue +56 -0
  115. package/src/web-components/smart-file-preview/info.ts +2 -0
  116. package/src/web-components/smart-file-preview/shared/const.ts +4 -0
  117. package/src/web-components/smart-file-preview/types.ts +38 -0
  118. package/src/web-components/smart-upload/index.ts +5 -0
  119. package/src/web-components/smart-upload/index.vue +101 -0
  120. package/src/web-components/smart-upload/info.ts +2 -0
  121. package/src/web-components/smart-upload/types.ts +28 -0
  122. package/tsconfig.json +15 -0
  123. package/types/auto-imports.d.ts +975 -0
  124. package/types/components.d.ts +14 -0
  125. package/types/env.d.ts +8 -0
  126. package/types/shims.d.ts +6 -0
  127. package/unocss.config.ts +23 -0
  128. 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,8 @@
1
+ .smart-docx-drive-page_custom-popover-item
2
+ @apply h10 flex-(~ items-center gap-2)
3
+ @apply font-sans text-(sm black)
4
+ @apply cursor-pointer select-none pl-2
5
+ @apply hover:bg-neutral-2
6
+
7
+ > .icon-wrap
8
+ @apply size-6 flex-(~ justify-center items-center)
@@ -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,2 @@
1
+ export const name = '云文档云服务页';
2
+ export const tag = 'smart-docx-drive-page';
@@ -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
+ });