jcloud/dashboard/src2/components/MarketplaceAppListing.vue
2025-04-12 17:39:38 +08:00

326 lines
8.8 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="space-y-12 rounded-md border px-5 py-4">
<div>
<div class="flex justify-between border-b pb-4">
<div>
<h2 class="text-lg font-medium text-gray-900">应用资料</h2>
<p class="mt-1 text-sm leading-6 text-gray-600">
这些信息将公开显示在市场上请确保输入正确的信息没有损坏的链接和图片
</p>
</div>
<Button :variant="editing ? 'solid' : 'subtle'" @click="updateListing"
>保存</Button
>
</div>
<div class="grid grid-cols-1 gap-x-5 border-b py-6 md:grid-cols-2">
<div class="border-r pr-6">
<span class="text-base font-medium">资料</span>
<div class="group relative my-4 flex">
<div class="flex flex-col">
<Avatar
size="3xl"
shape="square"
:label="app.pg.title"
:image="profileImageUrl"
/>
</div>
<FileUploader
@success="() => imageAddSuccess('资料照片已更新')"
@failure="imageAddFailure"
fileTypes="image/*"
:upload-args="{
pagetype: 'Marketplace App',
docname: app.pg.name,
method: 'jcloud.api.marketplace.update_app_image',
}"
>
<template
v-slot="{ openFileSelector, uploading, progress, error }"
>
<div class="ml-4">
<button
@click="openFileSelector()"
class="absolute inset-0 grid w-11.5 place-items-center rounded-lg bg-black text-xs font-medium text-white opacity-0 transition hover:opacity-50 focus:opacity-50 focus:outline-none"
:class="{ 'opacity-50': uploading }"
>
<span v-if="uploading">{{ progress }}%</span>
<span v-else>编辑</span>
</button>
</div>
</template>
</FileUploader>
</div>
<div class="pb-8 sm:col-span-4">
<FormControl
class="mt-4"
label="标题"
type="text"
@input="editing = true"
v-model="marketplaceApp.title"
/>
</div>
<div class="sm:col-span-4">
<span class="text-base font-medium">链接</span>
<div>
<FormControl
class="mt-4"
label="文档"
type="text"
@blur="validateLink('documentation')"
@input="editing = true"
v-model="marketplaceApp.documentation"
/>
<FormControl
class="mt-4"
label="网站"
type="text"
@blur="validateLink('website')"
@input="editing = true"
v-model="marketplaceApp.website"
/>
<FormControl
class="mt-4"
label="支持"
type="text"
@blur="validateLink('support')"
@input="editing = true"
v-model="marketplaceApp.support"
/>
<FormControl
class="mt-4"
label="服务条款"
type="text"
@blur="validateLink('terms_of_service')"
@input="editing = true"
v-model="marketplaceApp.terms_of_service"
/>
<FormControl
class="mt-4"
label="隐私政策"
type="text"
@blur="validateLink('privacy_policy')"
@input="editing = true"
v-model="marketplaceApp.privacy_policy"
/>
</div>
</div>
</div>
<div class="hidden md:block">
<div class="flex w-full">
<span class="text-base font-medium">截图和视频</span>
<FileUploader
class="ml-auto"
@success="() => imageAddSuccess('已添加截图')"
@failure="imageAddFailure"
fileTypes="image/*"
:upload-args="{
pagetype: 'Marketplace App',
docname: app.name,
method: 'jcloud.api.marketplace.add_app_screenshot',
}"
>
<template
v-slot="{ openFileSelector, uploading, progress, error }"
>
<Button
:loading="uploading"
@click="openFileSelector()"
icon-left="plus"
label="添加"
>
</Button>
</template>
</FileUploader>
</div>
<div
class="grid grid-cols-2 gap-x-4 gap-y-4 pt-4 lg:grid-cols-3 xl:grid-cols-4"
>
<Dropdown
class="w-fit"
v-for="(image, index) in marketplaceApp.screenshots"
:options="dropdownOptions(image)"
right
>
<template v-slot="{ open }">
<img
class="mx-1 h-24 w-36 cursor-pointer overflow-x-auto rounded-md object-cover"
:src="image"
/>
</template>
</Dropdown>
</div>
</div>
</div>
<div class="mt-6">
<span class="text-base font-medium">描述</span>
<FormControl
class="mt-4"
label="摘要"
type="textarea"
@input="editing = true"
v-model="marketplaceApp.description"
/>
<div class="mt-4">
<span class="text-xs text-gray-600">描述</span>
<TextEditor
class="mt-1 block w-full rounded border border-gray-100 bg-gray-100 px-2 py-1.5 text-base text-gray-800 placeholder-gray-500 transition-colors hover:border-gray-200 hover:bg-gray-200 focus:border-gray-500 focus:bg-white focus:shadow-sm focus:ring-0 focus-visible:ring-2 focus-visible:ring-gray-400"
ref="textEditor"
editor-class="rounded-b-lg max-w-[unset] prose-sm pb-[10vh]"
:content="marketplaceApp.long_description"
@change="marketplaceApp.long_description = $event"
:editable="editable"
:bubbleMenu="true"
>
</TextEditor>
</div>
</div>
</div>
</div>
</template>
<script>
import { TextEditor } from 'jingrow-ui';
import FileUploader from '@/components/FileUploader.vue';
import { toast } from 'vue-sonner';
import { getToastErrorMessage } from '../utils/toast';
export default {
name: 'MarketplaceAppOverview',
props: ['app'],
components: {
FileUploader,
TextEditor,
},
data() {
return {
editing: false,
editable: true,
marketplaceApp: {
title: this.app.pg.title,
website: '',
support: '',
documentation: '',
terms_of_service: '',
privacy_policy: '',
description: '',
long_description: '',
},
};
},
resources: {
updateListing() {
return {
url: 'jcloud.api.client.run_pg_method',
makeParams() {
return {
dt: 'Marketplace App',
dn: this.app.pg.name,
method: 'update_listing',
args: this.marketplaceApp,
};
},
};
},
listingData() {
return {
url: 'jcloud.api.client.run_pg_method',
makeParams() {
return {
dt: 'Marketplace App',
dn: this.app.pg.name,
method: 'listing_details',
};
},
auto: true,
onSuccess(response) {
this.marketplaceApp = { ...this.marketplaceApp, ...response.message };
},
onError(e) {
toast.error(getToastErrorMessage(e, 'Failed to fetch listing data'));
},
};
},
removeScreenshot() {
return {
url: 'jcloud.api.marketplace.remove_app_screenshot',
};
},
},
methods: {
imageAddSuccess(message) {
this.$resources.listingData.reload();
toast.success(message);
},
imageAddFailure(e) {
toast.error(e);
},
updateListing() {
toast.promise(this.$resources.updateListing.submit(), {
success: () => {
this.editing = false;
return '更新成功';
},
loading: '正在更新列表...',
error: (err) => {
return err.messages?.length
? err.messages.join('\n')
: err.message || '更新列表失败';
},
});
},
dropdownOptions(image) {
return [
{ label: '查看', onClick: () => window.open(image) },
{
label: '删除',
onClick: () => {
toast.promise(
this.$resources.removeScreenshot.submit({
name: this.app.pg.name,
file: image,
}),
{
loading: '正在删除截图...',
success: () => {
this.$resources.listingData.reload();
return '截图删除成功';
},
error: (err) => {
return err.messages?.length
? err.messages.join('\n')
: err.message || '删除截图失败';
},
},
);
},
},
];
},
validateLink(link) {
const value = this.marketplaceApp[link] ?? '';
// 用于验证URL格式的正则表达式
const urlPattern =
/^(https?:\/\/)?([\w\-]+\.)+[\w\-]{2,}(\/[\w\-._~:\/?#[\]@!$&'()*+,;=]*)?$/;
// 检查链接是否为空
if (!value.trim()) {
this.$toast.error(`${link.replace('_', ' ')} 链接为空`);
return false;
}
// 检查链接是否包含有效的URL
if (!urlPattern.test(value.trim())) {
this.$toast.error(`${link.replace('_', ' ')} 包含无效的URL`);
return false;
}
return true;
},
},
computed: {
profileImageUrl() {
return this.app.pg.image;
},
},
};
</script>