@smartos-lib/components 1.7.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ });