Feat: upload image

This commit is contained in:
Lazy 2023-11-29 22:48:06 +08:00
parent 64dc34e6eb
commit f390de613e
6 changed files with 109 additions and 19 deletions

View File

@ -167,10 +167,8 @@ export const uploadAddress = useDefineApi<
export const uploadFile = useDefineApi< export const uploadFile = useDefineApi<
{ {
data: FormData; data: FormData;
url: string;
onUploadProgress: Function;
}, },
{} any
>({ >({
method: "POST", method: "POST",
headers: { "Content-Type": "multipart/form-data" } headers: { "Content-Type": "multipart/form-data" }

View File

@ -191,10 +191,8 @@ export const uploadInstanceFile = useDefineApi<
code: string; code: string;
}; };
data: FormData; data: FormData;
url: string;
onUploadProgress: Function;
}, },
{} any
>({ >({
method: "POST", method: "POST",
headers: { "Content-Type": "multipart/form-data" } headers: { "Content-Type": "multipart/form-data" }

View File

@ -22,3 +22,14 @@ export const resetLayoutConfig = useDefineApi<any, void>({
url: "/api/overview/layout", url: "/api/overview/layout",
method: "DELETE" method: "DELETE"
}); });
export const uploadFile = useDefineApi<
{
data: FormData;
},
string
>({
method: "POST",
headers: { "Content-Type": "multipart/form-data" },
url: "/api/overview/upload_assets"
});

View File

@ -1,33 +1,60 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from "vue"; import { ref } from "vue";
import { $t as t } from "@/lang/i18n"; import { t } from "@/lang/i18n";
import { useAppToolsStore } from "@/stores/useAppToolsStore";
import { useLayoutContainerStore } from "@/stores/useLayoutContainerStore"; import { useLayoutContainerStore } from "@/stores/useLayoutContainerStore";
import CardPanel from "@/components/CardPanel.vue"; import CardPanel from "@/components/CardPanel.vue";
import type { LayoutCard } from "@/types/index"; import type { LayoutCard } from "@/types/index";
import { Empty } from "ant-design-vue"; import { Empty, message, type UploadProps } from "ant-design-vue";
import { UploadOutlined } from "@ant-design/icons-vue";
import { useLayoutCardTools } from "@/hooks/useCardTools"; import { useLayoutCardTools } from "@/hooks/useCardTools";
import { uploadFile } from "@/services/apis/layout";
import { useAppStateStore } from "@/stores/useAppStateStore";
const props = defineProps<{ const props = defineProps<{
card: LayoutCard; card: LayoutCard;
}>(); }>();
const { getMetaValue, setMetaValue } = useLayoutCardTools(props.card); const { getMetaValue, setMetaValue } = useLayoutCardTools(props.card);
const { containerState } = useLayoutContainerStore(); const { containerState } = useLayoutContainerStore();
const { isAdmin } = useAppStateStore();
const imgSrc = ref(getMetaValue("image", "")); const imgSrc = ref(getMetaValue("image", ""));
const { openInputDialog } = useAppToolsStore(); const open = ref(false);
const activeKey = ref("upload");
const percentComplete = ref(0);
const { state, execute } = uploadFile();
const beforeUpload: UploadProps["beforeUpload"] = async (file) => {
const uploadFormData = new FormData();
uploadFormData.append("file", file);
await execute({
data: uploadFormData,
timeout: 60 * 60 * 1000,
onUploadProgress: (progressEvent: any) => {
percentComplete.value = Math.round((progressEvent.loaded * 100) / progressEvent.total);
}
});
if (state.value) {
imgSrc.value = `${window.location.origin}/upload_files/${state.value}`;
setMetaValue("image", imgSrc.value);
percentComplete.value = 0;
message.success(t("TXT_CODE_773f36a0"));
open.value = false;
return false;
}
};
const save = async () => {
setMetaValue("image", imgSrc.value);
open.value = false;
};
const editImgSrc = async () => { const editImgSrc = async () => {
try { open.value = true;
imgSrc.value = (await openInputDialog(t("TXT_CODE_c8a51b2e"))) as string;
setMetaValue("image", imgSrc.value);
} catch (error) {}
}; };
</script> </script>
<template> <template>
<div style="width: 100%; position: relative"> <div style="width: 100%; height: 100%; position: relative">
<div v-if="imgSrc !== '' && containerState.isDesignMode" class="mask"> <div v-if="imgSrc !== '' && containerState.isDesignMode" class="mask">
<a-button type="primary" @click="editImgSrc()"> <a-button type="primary" @click="editImgSrc()">
{{ t("TXT_CODE_fd13f431") }} {{ t("TXT_CODE_fd13f431") }}
@ -40,13 +67,66 @@ const editImgSrc = async () => {
<template #description> <template #description>
<span>{{ t("TXT_CODE_635d051") }}</span> <span>{{ t("TXT_CODE_635d051") }}</span>
</template> </template>
<a-button type="primary" @click="editImgSrc()"> <a-button
:disabled="!containerState.isDesignMode || !isAdmin"
type="primary"
@click="editImgSrc()"
>
{{ t("TXT_CODE_589e091c") }} {{ t("TXT_CODE_589e091c") }}
</a-button> </a-button>
</a-empty> </a-empty>
</template> </template>
</CardPanel> </CardPanel>
</div> </div>
<a-modal v-model:open="open" :title="null" :closable="false" :destroy-on-close="true">
<a-tabs v-model:activeKey="activeKey">
<a-tab-pane key="upload" :tab="t('TXT_CODE_e00c858c')">
<a-progress
v-if="percentComplete > 0"
:stroke-color="{
'0%': '#49b3ff',
'100%': '#25f5b9'
}"
:percent="percentComplete"
class="mb-20"
/>
<a-upload
:max-count="1"
:disabled="percentComplete > 0"
:show-upload-list="false"
:before-upload="beforeUpload"
>
<a-button type="primary" :loading="percentComplete > 0">
<upload-outlined v-if="percentComplete === 0" />
{{
percentComplete > 0
? t("TXT_CODE_b625dbf0") + percentComplete + "%"
: t("TXT_CODE_e00c858c")
}}
</a-button>
</a-upload>
<a-typography class="mt-20">
<a-typography-title :level="5">{{ t("温馨提示") }}</a-typography-title>
<a-typography-paragraph>
<ol>
<li>{{ t("上传后将自动保存") }}</li>
<li>{{ t("你可以通过 “重置布局” 来清空你上传的所有文件") }}</li>
</ol>
</a-typography-paragraph>
</a-typography>
</a-tab-pane>
<a-tab-pane key="url" :tab="t('网络URL')" force-render>
<a-input v-model:value.lazy.trim="imgSrc" autofocus :placeholder="t('TXT_CODE_c8a51b2e')" />
</a-tab-pane>
</a-tabs>
<template #footer>
<a-button @click="open = false">{{ t("关闭") }}</a-button>
<a-button v-if="activeKey === 'url'" type="primary" @click="save">
{{ t("保存") }}
</a-button>
</template>
</a-modal>
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">

View File

@ -14,7 +14,10 @@ export default defineConfig({
target: "http://localhost:23333", target: "http://localhost:23333",
changeOrigin: true, changeOrigin: true,
ws: true ws: true
// rewrite: (path) => path.replace(/^\/api/, "") },
"/upload_files": {
target: "http://localhost:23333",
changeOrigin: true
}, },
"/socket.io": { "/socket.io": {
target: "ws://localhost:23333", target: "ws://localhost:23333",

View File

@ -169,7 +169,7 @@ _ / / / / /___ ____/ /_ / / / / /_/ /_ / / / /_/ /_ /_/ // __/ /
const koaStaticOptions = { const koaStaticOptions = {
maxAge: 10 * 24 * 60 * 60 //Cache for ten days. Changed files will not be load from cache. maxAge: 10 * 24 * 60 * 60 //Cache for ten days. Changed files will not be load from cache.
}; };
app.use(koaStatic(path.join(BASE_PATH, "public"), koaStaticOptions)); app.use(koaStatic(path.join(process.cwd(), "public"), koaStaticOptions));
// Websocket routing (useless for now) // Websocket routing (useless for now)
// import SocketService from "./app/service/socket_service"; // import SocketService from "./app/service/socket_service";