326 lines
8.8 KiB
Vue
326 lines
8.8 KiB
Vue
<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> |