feat: refine attachment management page ui

Signed-off-by: Ryan Wang <i@ryanc.cc>
pull/599/head
Ryan Wang 2022-08-12 16:15:08 +08:00
parent f201a93ab9
commit cdf93b002d
9 changed files with 766 additions and 337 deletions

View File

@ -40,6 +40,7 @@ import IconGitBranch from "~icons/ri/git-branch-line";
import IconStopCircle from "~icons/ri/stop-circle-line"; import IconStopCircle from "~icons/ri/stop-circle-line";
import IconForbidLine from "~icons/ri/forbid-line"; import IconForbidLine from "~icons/ri/forbid-line";
import IconCodeBoxLine from "~icons/ri/code-box-line"; import IconCodeBoxLine from "~icons/ri/code-box-line";
import IconDatabase2Line from "~icons/ri/database-2-line";
export { export {
IconDashboard, IconDashboard,
@ -84,4 +85,5 @@ export {
IconStopCircle, IconStopCircle,
IconForbidLine, IconForbidLine,
IconCodeBoxLine, IconCodeBoxLine,
IconDatabase2Line,
}; };

View File

@ -1,26 +1,27 @@
<script lang="ts" setup> <script lang="ts" setup>
import { import {
IconArrowDown, IconArrowDown,
IconArrowLeft,
IconArrowRight,
IconCheckboxFill, IconCheckboxFill,
IconDatabase2Line,
IconFolder,
IconGrid, IconGrid,
IconList, IconList,
IconMore,
IconPalette, IconPalette,
IconSettings, IconSettings,
IconUpload,
VButton, VButton,
VCard, VCard,
VModal,
VPageHeader, VPageHeader,
VPagination, VPagination,
VSpace, VSpace,
VTag, VTag,
} from "@halo-dev/components"; } from "@halo-dev/components";
import AttachmentDetailModal from "./components/AttachmentDetailModal.vue";
import AttachmentUploadModal from "./components/AttachmentUploadModal.vue";
import AttachmentSelectModal from "./components/AttachmentSelectModal.vue";
import AttachmentStrategiesModal from "./components/AttachmentStrategiesModal.vue";
import { onMounted, ref } from "vue"; import { onMounted, ref } from "vue";
import vueFilePond from "vue-filepond";
import "filepond/dist/filepond.min.css";
import FilePondPluginImagePreview from "filepond-plugin-image-preview";
import "filepond-plugin-image-preview/dist/filepond-plugin-image-preview.min.css";
import type { User } from "@halo-dev/api-client"; import type { User } from "@halo-dev/api-client";
import { apiClient } from "@halo-dev/admin-shared"; import { apiClient } from "@halo-dev/admin-shared";
@ -35,37 +36,15 @@ const viewTypes = [
}, },
]; ];
const FilePond = vueFilePond(FilePondPluginImagePreview);
const viewType = ref("grid"); const viewType = ref("grid");
const strategyVisible = ref(false); const strategyVisible = ref(false);
const attachmentSelectVisible = ref(false); const selectVisible = ref(false);
const uploadVisible = ref(false); const uploadVisible = ref(false);
const detailVisible = ref(false); const detailVisible = ref(false);
const checkAll = ref(false); const checkAll = ref(false);
const users = ref<User[]>([]); const users = ref<User[]>([]);
const strategies = ref([
{
id: "1",
name: "本地存储",
description: "~/.halo/uploads",
},
{
id: "2",
name: "阿里云 OSS",
description: "bucket/blog-attachments",
},
{
id: "3",
name: "阿里云 OSS",
description: "bucket/blog-photos",
},
]);
const selected = ref(strategies.value[0].id);
const attachments = Array.from(new Array(50), (_, index) => index).map( const attachments = Array.from(new Array(50), (_, index) => index).map(
(index) => { (index) => {
return { return {
@ -79,6 +58,30 @@ const attachments = Array.from(new Array(50), (_, index) => index).map(
} }
); );
const folders = [
{
name: "2022",
},
{
name: "2021",
},
{
name: "Photos",
},
{
name: "Documents",
},
{
name: "Videos",
},
{
name: "Pictures",
},
{
name: "Developer",
},
];
const handleFetchUsers = async () => { const handleFetchUsers = async () => {
try { try {
const { data } = await apiClient.extension.user.listv1alpha1User(); const { data } = await apiClient.extension.user.listv1alpha1User();
@ -93,292 +96,38 @@ onMounted(() => {
}); });
</script> </script>
<template> <template>
<AttachmentDetailModal v-model:visible="detailVisible" />
<AttachmentUploadModal v-model:visible="uploadVisible" />
<AttachmentSelectModal v-model:visible="selectVisible" />
<AttachmentStrategiesModal v-model:visible="strategyVisible" />
<VPageHeader title="附件库"> <VPageHeader title="附件库">
<template #icon> <template #icon>
<IconPalette class="mr-2 self-center" /> <IconPalette class="mr-2 self-center" />
</template> </template>
<template #actions> <template #actions>
<VSpace> <VSpace>
<VButton type="default" @click="attachmentSelectVisible = true" <VButton size="sm" @click="strategyVisible = true">
>选择 <template #icon>
<IconDatabase2Line class="h-full w-full" />
</template>
存储策略
</VButton>
<VButton size="sm">
<template #icon>
<IconSettings class="h-full w-full" />
</template>
设置
</VButton>
<VButton type="secondary" @click="uploadVisible = true">
<template #icon>
<IconUpload class="h-full w-full" />
</template>
上传
</VButton> </VButton>
<VButton type="secondary" @click="uploadVisible = true">上传</VButton>
</VSpace> </VSpace>
</template> </template>
</VPageHeader> </VPageHeader>
<VModal
v-model:visible="uploadVisible"
:body-class="['!p-0']"
:width="1024"
title="上传附件"
>
<div class="w-full">
<div class="flex flex-row">
<VCard
:bodyClass="['!p-0']"
class="border-r-1 w-72 border-t-0 border-l-0 border-b-0"
>
<fieldset>
<div class="-space-y-px divide-y divide-gray-100 bg-white">
<label
v-for="(strategy, index) in strategies"
:key="index"
:class="{
'bg-gray-50': selected === strategy.id,
}"
class="relative flex cursor-pointer p-4 focus:outline-none"
>
<input
v-model="selected"
:value="strategy.id"
class="mt-0.5 h-4 w-4 shrink-0 cursor-pointer border-gray-300 text-indigo-600 focus:ring-indigo-500"
name="privacy"
type="radio"
/>
<span class="ml-3 flex flex-1 flex-col">
<span
:class="{ 'font-bold': selected === strategy.id }"
class="block text-sm font-medium"
>
{{ strategy.name }}
</span>
<span class="block text-sm text-gray-400">
{{ strategy.description }}
</span>
</span>
<div class="self-center">
<IconSettings
class="cursor-pointer transition-all hover:text-blue-600"
@click.stop="strategyVisible = true"
/>
</div>
</label>
</div>
</fieldset>
<template #footer>
<FloatingDropdown>
<VButton block type="secondary"> 添加策略</VButton>
<template #popper>
<div class="w-72 p-4">
<ul class="space-y-1">
<li
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
>
<span class="truncate">本地</span>
</li>
<li
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
@click="strategyVisible = true"
>
<span class="truncate">阿里云 OSS</span>
</li>
<li
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
>
<span class="truncate">Amazon S3</span>
</li>
</ul>
</div>
</template>
</FloatingDropdown>
</template>
</VCard>
<div class="flex-1 p-4">
<file-pond
ref="pond"
accepted-file-types="image/jpeg, image/png"
label-idle="Drop files here..."
name="test"
server="/api"
v-bind:allow-multiple="true"
/>
</div>
</div>
</div>
</VModal>
<VModal
v-model:visible="strategyVisible"
:width="820"
title="添加存储策略"
></VModal>
<VModal v-model:visible="detailVisible" :width="1000" title="attachment-0">
<template #actions>
<div class="modal-header-action">
<IconArrowLeft />
</div>
<div class="modal-header-action">
<IconArrowRight />
</div>
</template>
<div class="overflow-hidden bg-white">
<div>
<dl>
<div
class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
>
<dt class="text-sm font-medium text-gray-900">原始内容</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
<img
class="w-full rounded sm:w-1/2"
src="https://picsum.photos/1000/700?random=1"
/>
</dd>
</div>
<div
class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
>
<dt class="text-sm font-medium text-gray-900">存储策略</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
阿里云/bucket/blog-attachments
</dd>
</div>
<div
class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
>
<dt class="text-sm font-medium text-gray-900">文件名称</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
attachment-0
</dd>
</div>
<div
class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
>
<dt class="text-sm font-medium text-gray-900">文件类型</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
image/jpeg
</dd>
</div>
<div
class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
>
<dt class="text-sm font-medium text-gray-900">文件大小</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
1.2 MB
</dd>
</div>
<div
class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
>
<dt class="text-sm font-medium text-gray-900">上传者</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
Ryan Wang
</dd>
</div>
<div
class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
>
<dt class="text-sm font-medium text-gray-900">上传时间</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
2020-01-01 12:00:00
</dd>
</div>
<div
class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
>
<dt class="text-sm font-medium text-gray-900">原始链接</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
https://picsum.photos/1000/700?random=1
</dd>
</div>
<div
class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
>
<dt class="text-sm font-medium text-gray-900">引用位置</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
<ul class="mt-2 space-y-2">
<li>
<div
class="inline-flex w-96 cursor-pointer flex-row gap-x-3 rounded border p-3 hover:border-primary"
>
<RouterLink
:to="{
name: 'Posts',
}"
class="font-medium text-gray-900 hover:text-blue-400"
>
Halo 1.5.3 发布了
</RouterLink>
<div class="text-xs">
<VSpace>
<VTag>文章</VTag>
</VSpace>
</div>
</div>
</li>
<li>
<div
class="inline-flex w-96 cursor-pointer flex-row gap-x-3 rounded border p-3 hover:border-primary"
>
<RouterLink
:to="{
name: 'Posts',
}"
class="font-medium text-gray-900 hover:text-blue-400"
>
Halo 1.5.2 发布
</RouterLink>
<div class="text-xs">
<VSpace>
<VTag>文章</VTag>
</VSpace>
</div>
</div>
</li>
</ul>
</dd>
</div>
</dl>
</div>
</div>
<template #footer>
<VButton type="default" @click="detailVisible = false">关闭</VButton>
</template>
</VModal>
<VModal
v-model:visible="attachmentSelectVisible"
:width="1240"
title="选择附件"
>
<div class="w-full">
<ul
class="grid grid-cols-2 gap-x-2 gap-y-3 sm:grid-cols-3 md:grid-cols-2 xl:grid-cols-8 2xl:grid-cols-8"
role="list"
>
<li
v-for="(attachment, index) in attachments"
:key="index"
class="relative"
>
<VCard :body-class="['!p-0']">
<div
class="group aspect-w-10 aspect-h-7 block w-full cursor-pointer overflow-hidden bg-gray-100"
>
<img
:src="attachment.url"
alt=""
class="pointer-events-none object-cover group-hover:opacity-75"
/>
</div>
<p
class="pointer-events-none block truncate px-2 py-1 text-sm font-medium text-gray-700"
>
{{ attachment.name }}
</p>
</VCard>
</li>
</ul>
</div>
<template #footer>
<VButton type="secondary">确定</VButton>
</template>
</VModal>
<div class="m-0 md:m-4"> <div class="m-0 md:m-4">
<div class="flex flex-col gap-2 sm:flex-row"> <div class="flex flex-col gap-2 sm:flex-row">
<div class="w-full"> <div class="w-full">
@ -588,40 +337,84 @@ onMounted(() => {
</div> </div>
</template> </template>
<div <div v-if="viewType === 'grid'">
v-if="viewType === 'grid'" <nav aria-label="Breadcrumb" class="mb-5 flex">
class="mt-2 grid grid-cols-3 gap-x-2 gap-y-3 sm:grid-cols-3 md:grid-cols-6 xl:grid-cols-8 2xl:grid-cols-12" <ol class="flex items-center space-x-4" role="list">
role="list" <li>
> <div class="flex items-center">
<VCard <a
v-for="(attachment, index) in attachments" class="text-sm font-medium text-gray-500 hover:text-gray-700"
:key="index" href="javascript:void(0)"
:body-class="['!p-0']" >
class="hover:shadow" Root
@click="detailVisible = true" </a>
</div>
</li>
<li>
<div class="flex items-center">
<div
class="flex h-5 w-5 flex-shrink-0 items-center justify-center text-gray-300"
>
<span> / </span>
</div>
<a
class="ml-4 text-sm font-medium text-gray-500 hover:text-gray-700"
href="javascript:void(0)"
>
Photos
</a>
</div>
</li>
</ol>
</nav>
<div class="mb-5 grid grid-cols-2 gap-x-2 gap-y-3 sm:grid-cols-6">
<div
v-for="(folder, index) in folders"
:key="index"
class="flex cursor-pointer items-center rounded-base bg-gray-100 p-2 text-gray-600 transition-all hover:bg-gray-200 hover:text-gray-900 hover:shadow-sm"
>
<div class="flex flex-1 items-center">
<IconFolder class="mr-2 h-4 w-4" />
<span class="text-sm">{{ folder.name }}</span>
</div>
<IconMore />
</div>
</div>
<div
class="mt-2 grid grid-cols-3 gap-x-2 gap-y-3 sm:grid-cols-3 md:grid-cols-6 xl:grid-cols-8 2xl:grid-cols-12"
role="list"
> >
<div class="relative bg-white"> <VCard
<div v-for="(attachment, index) in attachments"
class="group aspect-w-10 aspect-h-8 block h-full w-full cursor-pointer overflow-hidden bg-gray-100" :key="index"
> :body-class="['!p-0']"
<img class="hover:shadow"
:src="attachment.url" @click="detailVisible = true"
alt="" >
class="pointer-events-none object-cover group-hover:opacity-75" <div class="relative bg-white">
<div
class="group aspect-w-10 aspect-h-8 block h-full w-full cursor-pointer overflow-hidden bg-gray-100"
>
<img
:src="attachment.url"
alt=""
class="pointer-events-none object-cover group-hover:opacity-75"
/>
</div>
<p
class="pointer-events-none block truncate px-2 py-1 text-center text-xs font-medium text-gray-700"
>
{{ attachment.name }}
</p>
<IconCheckboxFill
v-if="checkAll"
class="absolute top-0.5 right-0.5"
/> />
</div> </div>
<p </VCard>
class="pointer-events-none block truncate px-2 py-1 text-center text-xs font-medium text-gray-700" </div>
>
{{ attachment.name }}
</p>
<IconCheckboxFill
v-if="checkAll"
class="absolute top-0.5 right-0.5"
/>
</div>
</VCard>
</div> </div>
<ul <ul

View File

@ -0,0 +1,94 @@
<script lang="ts" setup>
import { VAlert, VButton, VModal, VSpace } from "@halo-dev/components";
defineProps({
visible: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:visible", "close"]);
const onVisibleChange = (visible: boolean) => {
emit("update:visible", visible);
if (!visible) {
emit("close");
}
};
</script>
<template>
<VModal
:visible="visible"
:width="600"
title="阿里云 OSS 存储策略编辑"
@update:visible="onVisibleChange"
>
<VAlert class="mb-4" title="提示">
<template #description>
<p class="my-3 text-sm text-gray-600">
阿里云 OSS 产品文档<a
href="https://help.aliyun.com/product/31815.html"
target="_blank"
>https://help.aliyun.com/product/31815.html</a
>
</p>
</template>
</VAlert>
<FormKit id="alioss-strategy-form" type="form">
<FormKit label="名称" type="text" validation="required"></FormKit>
<FormKit label="Bucket" type="text" validation="required"></FormKit>
<FormKit label="EndPoint" type="text" validation="required"></FormKit>
<FormKit label="Access Key" type="text" validation="required"></FormKit>
<FormKit
label="Access Secret"
type="text"
validation="required"
></FormKit>
<FormKit
label="上传目录"
placeholder="如不填写,则默认上传到根目录"
type="text"
></FormKit>
<FormKit
:options="[
{ label: 'HTTPS', value: 'https' },
{ label: 'HTTP', value: 'http' },
]"
label="绑定域名协议"
type="select"
></FormKit>
<FormKit
label="绑定域名"
placeholder="如不设置,那么将使用 Bucket + EndPoint 作为域名"
type="text"
></FormKit>
<FormKit
:options="[
{ label: '服务端上传', value: 'server' },
{ label: '客户端上传', value: 'client' },
]"
label="上传方式"
type="select"
></FormKit>
<FormKit
help="使用半角逗号分隔"
label="允许上传的文件类型"
type="textarea"
value="jpg,png,gif"
></FormKit>
</FormKit>
<template #footer>
<VSpace>
<VButton
type="secondary"
@click="$formkit.submit('alioss-strategy-form')"
>
保存 +
</VButton>
<VButton @click="onVisibleChange(false)"> Esc</VButton>
</VSpace>
</template>
</VModal>
</template>

View File

@ -0,0 +1,174 @@
<script lang="ts" setup>
import {
IconArrowLeft,
IconArrowRight,
VButton,
VModal,
VSpace,
VTag,
} from "@halo-dev/components";
defineProps({
visible: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:visible", "close"]);
const onVisibleChange = (visible: boolean) => {
emit("update:visible", visible);
if (!visible) {
emit("close");
}
};
</script>
<template>
<VModal
:visible="visible"
:width="1000"
title="attachment-0"
@update:visible="onVisibleChange"
>
<template #actions>
<div class="modal-header-action">
<IconArrowLeft />
</div>
<div class="modal-header-action">
<IconArrowRight />
</div>
</template>
<div class="overflow-hidden bg-white">
<div>
<dl>
<div
class="bg-gray-50 px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
>
<dt class="text-sm font-medium text-gray-900">原始内容</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
<img
class="w-full rounded sm:w-1/2"
src="https://picsum.photos/1000/700?random=1"
/>
</dd>
</div>
<div
class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
>
<dt class="text-sm font-medium text-gray-900">存储策略</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
阿里云/bucket/blog-attachments
</dd>
</div>
<div
class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
>
<dt class="text-sm font-medium text-gray-900">存储位置</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
/photos
</dd>
</div>
<div
class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
>
<dt class="text-sm font-medium text-gray-900">文件名称</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
attachment-0
</dd>
</div>
<div
class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
>
<dt class="text-sm font-medium text-gray-900">文件类型</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
image/jpeg
</dd>
</div>
<div
class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
>
<dt class="text-sm font-medium text-gray-900">文件大小</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
1.2 MB
</dd>
</div>
<div
class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
>
<dt class="text-sm font-medium text-gray-900">上传者</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
Ryan Wang
</dd>
</div>
<div
class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
>
<dt class="text-sm font-medium text-gray-900">上传时间</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
2020-01-01 12:00:00
</dd>
</div>
<div
class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
>
<dt class="text-sm font-medium text-gray-900">原始链接</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
https://picsum.photos/1000/700?random=1
</dd>
</div>
<div
class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6"
>
<dt class="text-sm font-medium text-gray-900">引用位置</dt>
<dd class="mt-1 text-sm text-gray-900 sm:col-span-2 sm:mt-0">
<ul class="mt-2 space-y-2">
<li>
<div
class="inline-flex w-96 cursor-pointer flex-row gap-x-3 rounded border p-3 hover:border-primary"
>
<RouterLink
:to="{
name: 'Posts',
}"
class="font-medium text-gray-900 hover:text-blue-400"
>
Halo 1.5.3 发布了
</RouterLink>
<div class="text-xs">
<VSpace>
<VTag>文章</VTag>
</VSpace>
</div>
</div>
</li>
<li>
<div
class="inline-flex w-96 cursor-pointer flex-row gap-x-3 rounded border p-3 hover:border-primary"
>
<RouterLink
:to="{
name: 'Posts',
}"
class="font-medium text-gray-900 hover:text-blue-400"
>
Halo 1.5.2 发布
</RouterLink>
<div class="text-xs">
<VSpace>
<VTag>文章</VTag>
</VSpace>
</div>
</div>
</li>
</ul>
</dd>
</div>
</dl>
</div>
</div>
<template #footer>
<VButton type="default" @click="onVisibleChange(false)"></VButton>
</template>
</VModal>
</template>

View File

@ -0,0 +1,50 @@
<script lang="ts" setup>
import { VButton, VModal, VSpace } from "@halo-dev/components";
defineProps({
visible: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:visible", "close"]);
const onVisibleChange = (visible: boolean) => {
emit("update:visible", visible);
if (!visible) {
emit("close");
}
};
</script>
<template>
<VModal
:visible="visible"
:width="600"
title="本地存储策略编辑"
@update:visible="onVisibleChange"
>
<FormKit id="local-strategy-form" type="form">
<FormKit label="名称" type="text" validation="required"></FormKit>
<FormKit label="存储位置" type="text" validation="required"></FormKit>
<FormKit
help="使用半角逗号分隔"
label="允许上传的文件类型"
type="textarea"
value="jpg,png,gif"
></FormKit>
</FormKit>
<template #footer>
<VSpace>
<VButton
type="secondary"
@click="$formkit.submit('local-strategy-form')"
>
保存 +
</VButton>
<VButton @click="onVisibleChange(false)"> Esc</VButton>
</VSpace>
</template>
</VModal>
</template>

View File

@ -0,0 +1,73 @@
<script lang="ts" setup>
import { VButton, VCard, VModal } from "@halo-dev/components";
defineProps({
visible: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:visible", "close"]);
const attachments = Array.from(new Array(50), (_, index) => index).map(
(index) => {
return {
id: index,
name: `attachment-${index}`,
url: `https://picsum.photos/1000/700?random=${index}`,
size: "1.2MB",
type: "image/png",
strategy: "本地存储",
};
}
);
const onVisibleChange = (visible: boolean) => {
emit("update:visible", visible);
if (!visible) {
emit("close");
}
};
</script>
<template>
<VModal
:visible="visible"
:width="1240"
title="选择附件"
@update:visible="onVisibleChange"
>
<div class="w-full">
<ul
class="grid grid-cols-2 gap-x-2 gap-y-3 sm:grid-cols-3 md:grid-cols-2 xl:grid-cols-8 2xl:grid-cols-8"
role="list"
>
<li
v-for="(attachment, index) in attachments"
:key="index"
class="relative"
>
<VCard :body-class="['!p-0']">
<div
class="group aspect-w-10 aspect-h-7 block w-full cursor-pointer overflow-hidden bg-gray-100"
>
<img
:src="attachment.url"
alt=""
class="pointer-events-none object-cover group-hover:opacity-75"
/>
</div>
<p
class="pointer-events-none block truncate px-2 py-1 text-sm font-medium text-gray-700"
>
{{ attachment.name }}
</p>
</VCard>
</li>
</ul>
</div>
<template #footer>
<VButton type="secondary" @click="onVisibleChange(false)"></VButton>
</template>
</VModal>
</template>

View File

@ -0,0 +1,149 @@
<script lang="ts" setup>
import {
IconAddCircle,
IconMore,
VButton,
VModal,
VSpace,
} from "@halo-dev/components";
import AttachmentLocalStrategyEditingModal from "./AttachmentLocalStrategyEditingModal.vue";
import AttachmentAliOSSStrategyEditingModal from "./AttachmentAliOSSStrategyEditingModal.vue";
import { ref } from "vue";
defineProps({
visible: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:visible", "close"]);
const strategies = ref([
{
id: "1",
name: "本地存储",
description: "~/.halo/uploads",
},
{
id: "2",
name: "阿里云 OSS",
description: "bucket/blog-attachments",
},
{
id: "3",
name: "阿里云 OSS",
description: "bucket/blog-photos",
},
]);
const localStrategyVisible = ref(false);
const aliOSSStrategyVisible = ref(false);
function onVisibleChange(visible: boolean) {
emit("update:visible", visible);
if (!visible) {
emit("close");
}
}
</script>
<template>
<VModal
:visible="visible"
:width="750"
title="存储策略"
@update:visible="onVisibleChange"
>
<template #actions>
<FloatingDropdown>
<div v-tooltip="`添加存储策略`" class="modal-header-action">
<IconAddCircle />
</div>
<template #popper>
<div class="w-72 p-4">
<ul class="space-y-1">
<li
v-close-popper
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
@click="localStrategyVisible = true"
>
<span class="truncate">本地</span>
</li>
<li
v-close-popper
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
@click="aliOSSStrategyVisible = true"
>
<span class="truncate">阿里云 OSS</span>
</li>
<li
v-close-popper
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
>
<span class="truncate">Amazon S3</span>
</li>
</ul>
</div>
</template>
</FloatingDropdown>
</template>
<ul class="box-border h-full w-full divide-y divide-gray-100" role="list">
<li v-for="(strategy, index) in strategies" :key="index">
<div
class="relative block cursor-pointer px-4 py-3 transition-all hover:bg-gray-50"
>
<div class="relative flex flex-row items-center">
<div class="flex-1">
<div class="flex flex-col sm:flex-row">
<span
class="mr-0 truncate text-sm font-medium text-gray-900 sm:mr-2"
>
{{ strategy.name }}
</span>
</div>
<div class="mt-1 flex">
<span class="text-xs text-gray-500">
{{ strategy.description }}
</span>
</div>
</div>
<div class="flex">
<div
class="inline-flex flex-col flex-col-reverse items-end gap-4 sm:flex-row sm:items-center sm:gap-6"
>
<time class="text-sm text-gray-500" datetime="2020-01-07">
2020-01-07
</time>
<span class="cursor-pointer">
<FloatingDropdown>
<IconMore />
<template #popper>
<div class="w-48 p-2">
<VSpace class="w-full" direction="column">
<VButton v-close-popper block type="secondary">
编辑
</VButton>
<VButton v-close-popper block type="danger">
删除
</VButton>
</VSpace>
</div>
</template>
</FloatingDropdown>
</span>
</div>
</div>
</div>
</div>
</li>
</ul>
<template #footer>
<VButton @click="onVisibleChange(false)"> Esc</VButton>
</template>
</VModal>
<AttachmentLocalStrategyEditingModal v-model:visible="localStrategyVisible" />
<AttachmentAliOSSStrategyEditingModal
v-model:visible="aliOSSStrategyVisible"
/>
</template>

View File

@ -0,0 +1,94 @@
<script lang="ts" setup>
import { VModal } from "@halo-dev/components";
import vueFilePond from "vue-filepond";
import "filepond/dist/filepond.min.css";
import FilePondPluginImagePreview from "filepond-plugin-image-preview";
import "filepond-plugin-image-preview/dist/filepond-plugin-image-preview.min.css";
import { ref } from "vue";
const FilePond = vueFilePond(FilePondPluginImagePreview);
defineProps({
visible: {
type: Boolean,
default: false,
},
});
const emit = defineEmits(["update:visible", "close"]);
const strategies = ref([
{
id: "1",
name: "本地存储",
description: "~/.halo/uploads",
},
{
id: "2",
name: "阿里云 OSS",
description: "bucket/blog-attachments",
},
{
id: "3",
name: "阿里云 OSS",
description: "bucket/blog-photos",
},
]);
const onVisibleChange = (visible: boolean) => {
emit("update:visible", visible);
if (!visible) {
emit("close");
}
};
</script>
<template>
<VModal
:body-class="['!p-0']"
:visible="visible"
:width="600"
title="上传附件"
@update:visible="onVisibleChange"
>
<template #actions>
<FloatingDropdown>
<div v-tooltip="`选择存储策略`" class="modal-header-action">
<span class="text-sm">本地存储</span>
</div>
<template #popper>
<div class="w-72 p-4">
<ul class="space-y-1">
<li
v-for="(strategy, index) in strategies"
:key="index"
class="flex cursor-pointer flex-col rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
>
<span class="truncate">
{{ strategy.name }}
</span>
<span class="text-xs">
{{ strategy.description }}
</span>
</li>
<li
class="flex cursor-pointer items-center rounded px-3 py-2 text-sm text-gray-600 hover:bg-gray-100 hover:text-gray-900"
>
<span class="truncate">新增存储策略</span>
</li>
</ul>
</div>
</template>
</FloatingDropdown>
</template>
<div class="w-full p-4">
<file-pond
ref="pond"
accepted-file-types="image/jpeg, image/png"
label-idle="Drop files here..."
name="test"
server="/api"
v-bind:allow-multiple="true"
/>
</div>
</VModal>
</template>

View File

@ -23,6 +23,6 @@ import {
</VPageHeader> </VPageHeader>
<div class="editor"> <div class="editor">
<!--mock--> <!--mock-->
<RichTextEditor /> <RichTextEditor />
</div> </div>
</template> </template>