mirror of
https://github.com/MCSManager/MCSManager.git
synced 2025-01-12 14:54:34 +08:00
Feat: upload image
This commit is contained in:
parent
64dc34e6eb
commit
f390de613e
@ -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" }
|
||||||
|
@ -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" }
|
||||||
|
@ -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"
|
||||||
|
});
|
||||||
|
@ -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">
|
||||||
|
@ -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",
|
||||||
|
@ -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";
|
||||||
|
Loading…
Reference in New Issue
Block a user