@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,47 @@
|
|
1
|
+
<template>
|
2
|
+
<Header style="margin-bottom: 24px" @upload-success="onUploadSuccess">
|
3
|
+
<template #title>
|
4
|
+
<b v-if="meActiveMenuInfo?.filePath === '/'" text-xl>我的空间</b>
|
5
|
+
<el-scrollbar v-else class="w-full flex-grow">
|
6
|
+
<div flex="~ gap-1">
|
7
|
+
<div
|
8
|
+
v-for="(info, index) in meActiveMenuPathInfo" :key="info.id"
|
9
|
+
flex="~ items-center none gap-1" text-xl
|
10
|
+
class="[&>*]-flex-none"
|
11
|
+
:class="{ 'op-80': index < meActiveMenuPathInfo.length - 1 }"
|
12
|
+
>
|
13
|
+
<button
|
14
|
+
cursor-default rounded bg-transparent px-1
|
15
|
+
:class="{ 'cursor-pointer hover:bg-neutral-1': index < meActiveMenuPathInfo.length - 1 }"
|
16
|
+
@click="meActiveMenu = info.id"
|
17
|
+
>
|
18
|
+
{{ info.label === '/' ? '我的空间' : info.label }}
|
19
|
+
</button>
|
20
|
+
<i-ic-baseline-arrow-forward-ios v-if="index < meActiveMenuPathInfo.length - 1" />
|
21
|
+
</div>
|
22
|
+
</div>
|
23
|
+
</el-scrollbar>
|
24
|
+
</template>
|
25
|
+
</Header>
|
26
|
+
<el-scrollbar class="-mr4" view-style="padding-right: 16px" height="100%">
|
27
|
+
<DocxFileList
|
28
|
+
ref="fileListRef"
|
29
|
+
:file-path="meActiveMenuInfo?.filePath"
|
30
|
+
/>
|
31
|
+
</el-scrollbar>
|
32
|
+
</template>
|
33
|
+
|
34
|
+
<script lang="ts" setup>
|
35
|
+
import Header from '../components-private/Header.vue';
|
36
|
+
import DocxFileList from '../components/DocxFileList.vue';
|
37
|
+
import { menuStore } from '../stores/menu';
|
38
|
+
|
39
|
+
const fileListRef = ref<typeof DocxFileList>();
|
40
|
+
|
41
|
+
const { meActiveMenuInfo, dirTree, meActiveMenuPathInfo, meActiveMenu } = menuStore();
|
42
|
+
|
43
|
+
function onUploadSuccess() {
|
44
|
+
dirTree.execute();
|
45
|
+
fileListRef.value?.execute();
|
46
|
+
}
|
47
|
+
</script>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
<!--
|
2
|
+
基于 el-image 二次封装的图片组件
|
3
|
+
- 支持传入 Blob 对象进行显示
|
4
|
+
-->
|
5
|
+
|
6
|
+
<template>
|
7
|
+
<el-image v-bind="props" :src="src">
|
8
|
+
<template #error>
|
9
|
+
<slot name="error" />
|
10
|
+
</template>
|
11
|
+
</el-image>
|
12
|
+
</template>
|
13
|
+
|
14
|
+
<script lang="ts" setup>
|
15
|
+
import type { ImageProps } from 'element-plus';
|
16
|
+
|
17
|
+
const props = defineProps<Partial<ImageProps>>();
|
18
|
+
|
19
|
+
const blob = defineModel<Blob>('blob');
|
20
|
+
const blobUrl = useObjectUrl(blob);
|
21
|
+
|
22
|
+
const src = computed(() => {
|
23
|
+
if (blob.value) return blobUrl.value;
|
24
|
+
return props.src;
|
25
|
+
});
|
26
|
+
</script>
|
@@ -0,0 +1,62 @@
|
|
1
|
+
<!--
|
2
|
+
基于 el-popover 二次封装的弹出框
|
3
|
+
- 为 reference 插槽传入 visible, 弹出框的显示状态
|
4
|
+
- 更好的 web-components 兼容性
|
5
|
+
-->
|
6
|
+
|
7
|
+
<template>
|
8
|
+
<ElPopover
|
9
|
+
ref="popoverRef"
|
10
|
+
v-bind="props"
|
11
|
+
@before-enter="visible = true"
|
12
|
+
@before-leave="visible = false"
|
13
|
+
>
|
14
|
+
<template #reference>
|
15
|
+
<slot name="reference" :visible="visible" />
|
16
|
+
</template>
|
17
|
+
|
18
|
+
<slot />
|
19
|
+
</ElPopover>
|
20
|
+
</template>
|
21
|
+
|
22
|
+
<script lang="ts" setup>
|
23
|
+
import { ElPopover } from 'element-plus';
|
24
|
+
import type { PopoverProps } from 'element-plus';
|
25
|
+
|
26
|
+
const props = withDefaults(defineProps<Partial<Omit<PopoverProps, 'visible'>>>(), {
|
27
|
+
offset: 4,
|
28
|
+
showArrow: false,
|
29
|
+
teleported: true,
|
30
|
+
popperStyle: 'width: 180px; padding: 8px 0; border-radius: 8px',
|
31
|
+
});
|
32
|
+
|
33
|
+
const visible = ref<boolean>();
|
34
|
+
|
35
|
+
const popoverRef = ref<typeof ElPopover>();
|
36
|
+
|
37
|
+
// 当弹窗插入到 body 中时, 将下拉框中的 .cp-item 样式类改为外置样式类
|
38
|
+
wheneverEffectScopeImmediate(() => props.teleported && popoverRef.value?.popperRef?.contentRef, (contentRef: HTMLDivElement) => {
|
39
|
+
let stop: (() => void) | undefined;
|
40
|
+
|
41
|
+
function addClass() {
|
42
|
+
stop?.();
|
43
|
+
contentRef.querySelectorAll('.cp-item').forEach((item) => {
|
44
|
+
item.classList.add('smart-docx-drive-page_custom-popover-item');
|
45
|
+
});
|
46
|
+
startObservation();
|
47
|
+
}
|
48
|
+
|
49
|
+
function startObservation() {
|
50
|
+
stop = useMutationObserver(contentRef, addClass, { childList: true, attributes: true, subtree: true }).stop;
|
51
|
+
}
|
52
|
+
|
53
|
+
addClass();
|
54
|
+
});
|
55
|
+
</script>
|
56
|
+
|
57
|
+
<style lang="sass">
|
58
|
+
@import '../external-style/custom-popover'
|
59
|
+
|
60
|
+
.cp-item
|
61
|
+
@extend .smart-docx-drive-page_custom-popover-item
|
62
|
+
</style>
|
@@ -0,0 +1,99 @@
|
|
1
|
+
<template>
|
2
|
+
<div
|
3
|
+
class="menu-item group/dir"
|
4
|
+
v-bind="attrs"
|
5
|
+
:class="{ active: isActive }"
|
6
|
+
@click="info && (meActiveMenu = info.id)"
|
7
|
+
>
|
8
|
+
<div w-4.5 flex="~ justify-center items-center" pos="relative left-1.5">
|
9
|
+
<el-button v-if="showOpen" link @click.stop="isOpen = !isOpen">
|
10
|
+
<i-akar-icons-triangle-right-fill class="size-4 transition-all" :class="{ 'rotate-90': isOpen }" />
|
11
|
+
</el-button>
|
12
|
+
</div>
|
13
|
+
|
14
|
+
<div class="munu-item-icon">
|
15
|
+
<slot v-if="slots.icon" name="icon" :isActive="isActive" />
|
16
|
+
<i-ph-folder-fill v-else-if="isActive" class="size-5" />
|
17
|
+
<i-ph-folder v-else class="size-5" />
|
18
|
+
</div>
|
19
|
+
|
20
|
+
<span flex-grow>{{ name ?? info?.label }}</span>
|
21
|
+
|
22
|
+
<CustomPopover v-if="!hiddenActions" placement="bottom-start" trigger="click" :offset="2">
|
23
|
+
<template #reference="{ visible }">
|
24
|
+
<button :class="{ 'bg-neutral-3! flex!': visible }" class="hidden group-hover/dir:flex" size-6 flex="justify-center items-center" bg="transparent hover:neutral-3" transition outline-none rounded @click.stop>
|
25
|
+
<i-ph-dots-three-bold class="size-5 c-neutral-5" />
|
26
|
+
</button>
|
27
|
+
</template>
|
28
|
+
|
29
|
+
<div class="cp-item" @click="toRename(info!)"><div class="icon-wrap"><i-ph-note-pencil-duotone class="size-4.8" /></div>重命名</div>
|
30
|
+
<div class="cp-item" @click="toDelete(info!)"><div class="icon-wrap"><i-material-symbols-delete-outline class="size-5" /></div>删除</div>
|
31
|
+
</CustomPopover>
|
32
|
+
</div>
|
33
|
+
|
34
|
+
<template v-if="isOpen">
|
35
|
+
<DocxDir v-for="item in info?.children" :key="item.id" :info="item" :level="level + 1" :style="{ paddingLeft: `${level * 16}px` }" />
|
36
|
+
</template>
|
37
|
+
</template>
|
38
|
+
|
39
|
+
<script lang="ts" setup>
|
40
|
+
import type { DirInfo } from '../apis/folder';
|
41
|
+
import { menuStore } from '../stores/menu';
|
42
|
+
import { toDeleteFolder, toRenameFolder } from '../utils/file-actions';
|
43
|
+
import CustomPopover from './CustomPopover.vue';
|
44
|
+
|
45
|
+
interface Props {
|
46
|
+
name?: string
|
47
|
+
info?: DirInfo
|
48
|
+
showOpen?: boolean
|
49
|
+
level?: number
|
50
|
+
hiddenActions?: boolean
|
51
|
+
}
|
52
|
+
|
53
|
+
const props = withDefaults(defineProps<Props>(), {
|
54
|
+
showOpen: true,
|
55
|
+
level: 1,
|
56
|
+
});
|
57
|
+
const slots = defineSlots<{
|
58
|
+
icon?: (props: { isActive: boolean }) => any
|
59
|
+
}>();
|
60
|
+
|
61
|
+
const attrs = useAttrs();
|
62
|
+
|
63
|
+
const isOpen = ref(false);
|
64
|
+
|
65
|
+
const { meActiveMenu, dirTree } = menuStore();
|
66
|
+
|
67
|
+
const isActive = computed(() => meActiveMenu.value === props.info?.id);
|
68
|
+
|
69
|
+
function toRename(info: DirInfo) {
|
70
|
+
toRenameFolder(info).then(() => {
|
71
|
+
dirTree.execute();
|
72
|
+
});
|
73
|
+
}
|
74
|
+
|
75
|
+
function toDelete(info: DirInfo) {
|
76
|
+
toDeleteFolder(info).then(() => {
|
77
|
+
dirTree.execute();
|
78
|
+
});
|
79
|
+
}
|
80
|
+
</script>
|
81
|
+
|
82
|
+
<script lang="ts">
|
83
|
+
export default defineComponent({
|
84
|
+
name: 'DocxDir',
|
85
|
+
});
|
86
|
+
</script>
|
87
|
+
|
88
|
+
<style lang="sass" scoped>
|
89
|
+
.menu-item
|
90
|
+
@apply h-10 flex items-center gap-2 relative
|
91
|
+
@apply text-15px cursor-pointer select-none rounded pr-2
|
92
|
+
@apply hover:bg-neutral-2
|
93
|
+
|
94
|
+
&.active
|
95
|
+
@apply c-blue-6 bg-blue-6/10
|
96
|
+
|
97
|
+
> .munu-item-icon
|
98
|
+
@apply size-5.5 flex justify-center items-center
|
99
|
+
</style>
|
@@ -0,0 +1,132 @@
|
|
1
|
+
<!-- 云文档 -->
|
2
|
+
|
3
|
+
<template>
|
4
|
+
<ElDialog ref="dialogRef" v-model="visible" :z-index="8888" append-to-body>
|
5
|
+
<template #header>
|
6
|
+
<div flex="~ col items-start gap-1">
|
7
|
+
<div c-black>{{ fileName || '未命名文档' }}</div>
|
8
|
+
<div text="xs neutral-6/90" bg="neutral-2/60" rounded p="x1 y.5" m--1>{{ (updateDoc.isLoading || updateDocTitle.isLoading) ? '保存中...' : '已经保存到云端' }}</div>
|
9
|
+
</div>
|
10
|
+
</template>
|
11
|
+
|
12
|
+
<el-scrollbar height="100%" view-style="display: flex; min-height: 100%">
|
13
|
+
<div w-full max-w-221.5 mxa p="x16.5 t5">
|
14
|
+
<!-- 标题 -->
|
15
|
+
<div
|
16
|
+
ref="titleRef"
|
17
|
+
class="empty:after-(content-['未命名文档'] c-neutral pointer-events-none)"
|
18
|
+
text-3xl font-bold lh-normal break-all c-black b-b="1 solid neutral-2" mb-5 py-5
|
19
|
+
contenteditable="plaintext-only"
|
20
|
+
@keydown.enter.prevent
|
21
|
+
@paste.prevent="removeNewlines"
|
22
|
+
/>
|
23
|
+
<!-- 文档内容 -->
|
24
|
+
<smart-docx-editor v-if="visible" ref="docxEditorRef" .initialValue="content" />
|
25
|
+
</div>
|
26
|
+
</el-scrollbar>
|
27
|
+
</ElDialog>
|
28
|
+
</template>
|
29
|
+
|
30
|
+
<script lang="ts" setup>
|
31
|
+
import type { Descendant } from 'slate';
|
32
|
+
import { ElDialog } from 'element-plus';
|
33
|
+
import { useMessage } from 'naive-ui';
|
34
|
+
import { useJsonParse } from '@smartos-lib/utils';
|
35
|
+
import { debounce } from 'lodash-es';
|
36
|
+
import { useFullscreenElDialog } from '../composables/useFullscreenElDialog';
|
37
|
+
import { createOnlineDocx, getOnlineDocx, updateOnlineDocx } from '../apis/doc';
|
38
|
+
import { type FileInfo, renameFile } from '../apis/file';
|
39
|
+
|
40
|
+
const emit = defineEmits(['updated']);
|
41
|
+
|
42
|
+
const dialogRef = ref<typeof ElDialog>();
|
43
|
+
const titleRef = ref<HTMLDivElement>();
|
44
|
+
const docxEditorRef = ref();
|
45
|
+
|
46
|
+
const message = useMessage();
|
47
|
+
|
48
|
+
const visible = ref(false);
|
49
|
+
const id = ref<string>();
|
50
|
+
const fileName = ref<string>('');
|
51
|
+
const content = shallowRef<Descendant[]>();
|
52
|
+
|
53
|
+
const updateDocTitleParams = () => ({ userFileId: id.value!, fileName: fileName.value || '未命名文档' });
|
54
|
+
const updateDocParams = () => ({ userFileId: id.value!, docContext: { value: content.value } });
|
55
|
+
|
56
|
+
const createDoc = createOnlineDocx();
|
57
|
+
const getDoc = getOnlineDocx();
|
58
|
+
const updateDocTitle = renameFile(updateDocTitleParams);
|
59
|
+
const updateDoc = updateOnlineDocx(updateDocParams);
|
60
|
+
|
61
|
+
/** 禁止粘贴换行 */
|
62
|
+
function removeNewlines(event: ClipboardEvent) {
|
63
|
+
const text = event.clipboardData?.getData('text/plain').replace(/(\r\n|\n|\r)/gm, "") ?? '';
|
64
|
+
const selection = window.getSelection();
|
65
|
+
|
66
|
+
if (selection?.getRangeAt && selection.rangeCount) {
|
67
|
+
const range = selection.getRangeAt(0);
|
68
|
+
range.deleteContents();
|
69
|
+
|
70
|
+
const textNode = document.createTextNode(text);
|
71
|
+
range.insertNode(textNode);
|
72
|
+
|
73
|
+
range.setStart(textNode, textNode.length);
|
74
|
+
range.setEnd(textNode, textNode.length);
|
75
|
+
|
76
|
+
selection.removeAllRanges();
|
77
|
+
selection.addRange(range);
|
78
|
+
}
|
79
|
+
}
|
80
|
+
|
81
|
+
useFullscreenElDialog(dialogRef);
|
82
|
+
|
83
|
+
useEventListener(titleRef, ['input', 'paste'], () => {
|
84
|
+
fileName.value = titleRef.value?.textContent?.trim() || '';
|
85
|
+
});
|
86
|
+
|
87
|
+
useEventListener(docxEditorRef, 'change', (event: CustomEvent<[Descendant[]]>) => {
|
88
|
+
content.value = event.detail[0] ?? [];
|
89
|
+
});
|
90
|
+
|
91
|
+
whenever(() => (createDoc.isLoading || getDoc.isLoading), (_, __, onCleanup) => {
|
92
|
+
const loading = message.loading(`请稍后, 正在${createDoc.isLoading ? '新建' : '加载'}文档...`, { duration: 0 });
|
93
|
+
onCleanup(() => loading.destroy());
|
94
|
+
});
|
95
|
+
|
96
|
+
wheneverEffectScope(id, () => {
|
97
|
+
watchDeep(updateDocTitleParams, debounce(updateDocTitle.execute, 1000, { maxWait: 1000 }));
|
98
|
+
watchDeep(updateDocParams, debounce(updateDoc.execute, 1000, { maxWait: 1000 }));
|
99
|
+
});
|
100
|
+
|
101
|
+
whenever(logicNot(visible), () => {
|
102
|
+
updateDocTitle.execute().then(() => emit('updated'));
|
103
|
+
updateDoc.execute();
|
104
|
+
|
105
|
+
id.value = undefined;
|
106
|
+
fileName.value = '';
|
107
|
+
content.value = [];
|
108
|
+
});
|
109
|
+
|
110
|
+
defineExpose({
|
111
|
+
createDoc: async () => {
|
112
|
+
if (createDoc.isLoading) return;
|
113
|
+
|
114
|
+
id.value = (await createDoc.execute({ fileName: '未命名文档' })).data.data;
|
115
|
+
visible.value = true;
|
116
|
+
},
|
117
|
+
updateDoc: async (file: FileInfo) => {
|
118
|
+
if (getDoc.isLoading) return;
|
119
|
+
|
120
|
+
await getDoc.execute({ userFileId: file.userFileId });
|
121
|
+
|
122
|
+
id.value = file.userFileId;
|
123
|
+
fileName.value = file.fileName;
|
124
|
+
content.value = useJsonParse(getDoc.data?.data.docContext, { value: [] }).value;
|
125
|
+
|
126
|
+
visible.value = true;
|
127
|
+
nextTick(() => {
|
128
|
+
titleRef.value!.textContent = fileName.value;
|
129
|
+
});
|
130
|
+
},
|
131
|
+
});
|
132
|
+
</script>
|
@@ -0,0 +1,41 @@
|
|
1
|
+
<!--
|
2
|
+
弹出框中的下载功能
|
3
|
+
-->
|
4
|
+
|
5
|
+
<template>
|
6
|
+
<div
|
7
|
+
class="cp-item relative"
|
8
|
+
:class="{
|
9
|
+
'c-emerald-5!': download.isLoading,
|
10
|
+
}"
|
11
|
+
@click="download.isLoading || download.execute()"
|
12
|
+
>
|
13
|
+
<div class="icon-wrap">
|
14
|
+
<i-line-md-downloading-loop v-if="download.isLoading" class="size-5" />
|
15
|
+
<i-material-symbols-download-rounded v-else class="size-6" />
|
16
|
+
</div>
|
17
|
+
下载<template v-if="download.isLoading">中</template>
|
18
|
+
|
19
|
+
<!-- 上传进度 / 背景 -->
|
20
|
+
<div h-full pos="absolute top-0 left-0" z--1 bg-neutral-1 transition-width v-bind="progressStyle" />
|
21
|
+
<!-- 上传进度 / 进度条 -->
|
22
|
+
<div h-2px pos="absolute bottom-0 left-0" b-b="2 solid emerald-5" z-1 transition-width :class="{ 'b-red!': download.error }" v-bind="progressStyle" />
|
23
|
+
</div>
|
24
|
+
</template>
|
25
|
+
|
26
|
+
<script lang="ts" setup>
|
27
|
+
import type { FileInfo } from '../apis/file';
|
28
|
+
|
29
|
+
const props = defineProps<{ file: FileInfo }>();
|
30
|
+
|
31
|
+
const download = toRef(props.file, 'download');
|
32
|
+
const progress = computed(() => toValue(props.file.downloadProgress));
|
33
|
+
|
34
|
+
const progressStyle = computed(() => {
|
35
|
+
return download.value.error
|
36
|
+
? { class: 'w-full' }
|
37
|
+
: download.value.isLoading
|
38
|
+
? { class: 'min-w-2px', style: { width: `${progress.value}%` } }
|
39
|
+
: {};
|
40
|
+
});
|
41
|
+
</script>
|
@@ -0,0 +1,156 @@
|
|
1
|
+
<template>
|
2
|
+
<el-table
|
3
|
+
v-loading="fileList.isLoading"
|
4
|
+
class="file-list-table text-sm"
|
5
|
+
:data="fileList.data?.data.records" row-key="userFileId" size="large"
|
6
|
+
@row-click="toPreview"
|
7
|
+
>
|
8
|
+
<!-- 预览 -->
|
9
|
+
<el-table-column v-slot="{ row }" width="36" class-name="preview">
|
10
|
+
<i-ri-file-text-fill v-if="row.fileType === 2" class="size-6 c-blue-5" />
|
11
|
+
<template v-else-if="ImageExtEnum.includes(row.extendName)">
|
12
|
+
<i-svg-spinners-pulse-2 v-if="row.imagePreview.isLoading" />
|
13
|
+
<CustomImage v-else class="size-6" :blob="row.imagePreview.data">
|
14
|
+
<template #error>
|
15
|
+
<FileIcon class="size-6! text-6!" :ext="row.extendName" />
|
16
|
+
</template>
|
17
|
+
</CustomImage>
|
18
|
+
</template>
|
19
|
+
<FileIcon v-else class="size-6! text-6!" :ext="row.extendName" />
|
20
|
+
</el-table-column>
|
21
|
+
<!-- 名称 -->
|
22
|
+
<el-table-column v-slot="{ row }" prop="fileName" label="名称" min-width="200">
|
23
|
+
<div flex whitespace-nowrap>
|
24
|
+
<div truncate>{{ row.fileName }}</div>
|
25
|
+
<div v-if="row.extendName">.{{ row.extendName }}</div>
|
26
|
+
</div>
|
27
|
+
</el-table-column>
|
28
|
+
<!-- 所有者 -->
|
29
|
+
<el-table-column prop="userId" label="所有者" width="190" />
|
30
|
+
<!-- 大小 -->
|
31
|
+
<el-table-column v-slot="{ row }" prop="fileSize" label="大小" width="100">
|
32
|
+
{{ isNumber(row.fileSize) ? prettyBytes(row.fileSize) : row.fileSize }}
|
33
|
+
</el-table-column>
|
34
|
+
<!-- 上传时间 -->
|
35
|
+
<el-table-column prop="uploadTime" label="上传时间" width="180" />
|
36
|
+
<!-- 操作 -->
|
37
|
+
<el-table-column v-slot="{ row }" width="48" fixed="right" class-name="actions">
|
38
|
+
<CustomPopover placement="bottom-end" trigger="click" :offset="2">
|
39
|
+
<template #reference="{ visible }">
|
40
|
+
<button :class="{ 'bg-neutral-2!': visible }" size-6 flex="~ justify-center items-center" bg="transparent hover:neutral-2" transition outline-none rounded @click.stop>
|
41
|
+
<i-ph-dots-three-bold class="size-5" />
|
42
|
+
</button>
|
43
|
+
</template>
|
44
|
+
|
45
|
+
<DocxDownloadPopoverItem :file="row" />
|
46
|
+
<div class="cp-item" @click="toRename(row)"><div class="icon-wrap"><i-ph-note-pencil-duotone class="size-4.8" /></div>重命名</div>
|
47
|
+
<div class="cp-item" @click="toDelete(row)"><div class="icon-wrap"><i-material-symbols-delete-outline class="size-5" /></div>删除</div>
|
48
|
+
</CustomPopover>
|
49
|
+
</el-table-column>
|
50
|
+
</el-table>
|
51
|
+
|
52
|
+
<DocxPreview ref="previewRef" />
|
53
|
+
<DocxDoc ref="updateDocRef" @updated="fileList.execute()" />
|
54
|
+
</template>
|
55
|
+
|
56
|
+
<script lang="tsx" setup>
|
57
|
+
import type { MessageReactive } from 'naive-ui';
|
58
|
+
import type { CancelTokenSource } from 'axios';
|
59
|
+
import { ImageExtEnum } from '@smartos-lib/types';
|
60
|
+
import { isNumber, leastRun } from 'mixte';
|
61
|
+
import { NText, useMessage } from 'naive-ui';
|
62
|
+
import prettyBytes from 'pretty-bytes';
|
63
|
+
import { pick } from 'lodash-es';
|
64
|
+
import { getConfigProvider } from '@smartos-lib/core';
|
65
|
+
import { downloadFile, getFileList } from '../apis/file';
|
66
|
+
import type { FileInfo } from '../apis/file';
|
67
|
+
import { toDeleteFile, toRenameFile } from '../utils/file-actions';
|
68
|
+
import FileIcon from './FileIcon.vue';
|
69
|
+
import CustomImage from './CustomImage.vue';
|
70
|
+
import CustomPopover from './CustomPopover.vue';
|
71
|
+
import DocxDownloadPopoverItem from './DocxDownloadPopoverItem.vue';
|
72
|
+
import DocxPreview from './DocxPreview.vue';
|
73
|
+
import DocxDoc from './DocxDoc.vue';
|
74
|
+
|
75
|
+
const props = defineProps<{ filePath?: string }>();
|
76
|
+
|
77
|
+
const previewRef = ref<typeof DocxPreview>();
|
78
|
+
const updateDocRef = ref<typeof DocxDoc>();
|
79
|
+
|
80
|
+
const message = useMessage();
|
81
|
+
|
82
|
+
const fileList = getFileList(() => ({ filePath: props.filePath }));
|
83
|
+
|
84
|
+
function toRename(file: FileInfo) {
|
85
|
+
toRenameFile(file).then(() => {
|
86
|
+
fileList.execute();
|
87
|
+
});
|
88
|
+
}
|
89
|
+
|
90
|
+
function toDelete(file: FileInfo) {
|
91
|
+
toDeleteFile(file).then(() => {
|
92
|
+
fileList.execute();
|
93
|
+
});
|
94
|
+
}
|
95
|
+
|
96
|
+
// Todo: 移动到 DocxPreview 组件内
|
97
|
+
const toPreview = (() => {
|
98
|
+
const previewProgress = ref(0);
|
99
|
+
let previewMessage: MessageReactive | undefined;
|
100
|
+
let previewCancelToken: CancelTokenSource | undefined;
|
101
|
+
|
102
|
+
function cancelPreview() {
|
103
|
+
previewProgress.value = 0;
|
104
|
+
previewMessage?.destroy();
|
105
|
+
previewCancelToken?.cancel();
|
106
|
+
previewCancelToken = getConfigProvider('request')!.CancelToken.source();
|
107
|
+
}
|
108
|
+
|
109
|
+
return async (file: FileInfo) => {
|
110
|
+
cancelPreview();
|
111
|
+
|
112
|
+
if (file.fileType === 2) {
|
113
|
+
updateDocRef.value?.updateDoc(file);
|
114
|
+
return;
|
115
|
+
}
|
116
|
+
|
117
|
+
previewMessage = message.loading( // @ts-expect-error
|
118
|
+
() => <div>正在加载预览文件: <NText type="primary">{ file.fileFullName }</NText> <NText code>{previewProgress.value}%</NText></div>,
|
119
|
+
{ duration: 0 },
|
120
|
+
);
|
121
|
+
|
122
|
+
try {
|
123
|
+
const download = downloadFile(pick(file, ['userFileId']), { progress: previewProgress, cancelToken: previewCancelToken!.token });
|
124
|
+
await leastRun(360, () => download.execute());
|
125
|
+
previewRef.value?.previewFile(file.fileFullName, download.data!);
|
126
|
+
}
|
127
|
+
catch (error) {}
|
128
|
+
finally {
|
129
|
+
cancelPreview();
|
130
|
+
}
|
131
|
+
};
|
132
|
+
})();
|
133
|
+
|
134
|
+
watchImmediate(() => props.filePath, () => {
|
135
|
+
fileList.execute();
|
136
|
+
});
|
137
|
+
|
138
|
+
defineExpose({
|
139
|
+
execute: fileList.execute,
|
140
|
+
});
|
141
|
+
</script>
|
142
|
+
|
143
|
+
<style lang="sass" scoped>
|
144
|
+
// 预览单元格, 操作单元格
|
145
|
+
.file-list-table ::v-deep .smart-table__body-wrapper .smart-table__cell:is(.preview, .actions) > .cell
|
146
|
+
@apply size-full flex-(~ justify-center items-center)
|
147
|
+
@apply pos-(absolute top-0 left-0) px-0
|
148
|
+
|
149
|
+
// 预览单元格
|
150
|
+
.file-list-table ::v-deep .smart-table__body-wrapper .smart-table__cell.preview > .cell
|
151
|
+
@apply justify-end
|
152
|
+
|
153
|
+
// 表格行
|
154
|
+
.file-list-table ::v-deep .smart-table__row
|
155
|
+
@apply cursor-pointer
|
156
|
+
</style>
|
@@ -0,0 +1,33 @@
|
|
1
|
+
<!-- 文件预览 -->
|
2
|
+
|
3
|
+
<template>
|
4
|
+
<ElDialog ref="dialogRef" v-model="visible" :title="fileName" :z-index="8888" append-to-body>
|
5
|
+
<smart-file-preview v-if="file" ref="filePreviewRef" />
|
6
|
+
</ElDialog>
|
7
|
+
</template>
|
8
|
+
|
9
|
+
<script lang="ts" setup>
|
10
|
+
import { ElDialog } from 'element-plus';
|
11
|
+
import { useFullscreenElDialog } from '../composables/useFullscreenElDialog';
|
12
|
+
import type { SmartFilePreviewElement } from '@/web-components/smart-file-preview/index';
|
13
|
+
|
14
|
+
const dialogRef = ref<typeof ElDialog>();
|
15
|
+
const filePreviewRef = ref<SmartFilePreviewElement>();
|
16
|
+
|
17
|
+
const visible = ref(false);
|
18
|
+
const fileName = ref('');
|
19
|
+
const file = shallowRef<Blob>();
|
20
|
+
|
21
|
+
useFullscreenElDialog(dialogRef);
|
22
|
+
|
23
|
+
defineExpose({
|
24
|
+
previewFile: (_fileName: string, _file: Blob) => {
|
25
|
+
visible.value = true;
|
26
|
+
fileName.value = _fileName;
|
27
|
+
file.value = _file;
|
28
|
+
nextTick(() => {
|
29
|
+
filePreviewRef.value?.previewFile(_fileName, _file);
|
30
|
+
});
|
31
|
+
},
|
32
|
+
});
|
33
|
+
</script>
|