mirror of https://github.com/halo-dev/halo
Merge pull request #6729 from guqing/feature/5851
feat: support deleting posts in user centerpull/6771/head^2
commit
dfbab283ef
|
@ -15117,6 +15117,38 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/apis/uc.api.content.halo.run/v1alpha1/posts/{name}/recycle": {
|
||||||
|
"delete": {
|
||||||
|
"description": "Move my post to recycle bin.",
|
||||||
|
"operationId": "RecycleMyPost",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Post name",
|
||||||
|
"in": "path",
|
||||||
|
"name": "name",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"default": {
|
||||||
|
"content": {
|
||||||
|
"*/*": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "default response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"PostV1alpha1Uc"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/apis/uc.api.content.halo.run/v1alpha1/posts/{name}/unpublish": {
|
"/apis/uc.api.content.halo.run/v1alpha1/posts/{name}/unpublish": {
|
||||||
"put": {
|
"put": {
|
||||||
"description": "Unpublish my post.",
|
"description": "Unpublish my post.",
|
||||||
|
|
|
@ -412,6 +412,38 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/apis/uc.api.content.halo.run/v1alpha1/posts/{name}/recycle": {
|
||||||
|
"delete": {
|
||||||
|
"description": "Move my post to recycle bin.",
|
||||||
|
"operationId": "RecycleMyPost",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"description": "Post name",
|
||||||
|
"in": "path",
|
||||||
|
"name": "name",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"default": {
|
||||||
|
"content": {
|
||||||
|
"*/*": {
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/components/schemas/Post"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"description": "default response"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"tags": [
|
||||||
|
"PostV1alpha1Uc"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
"/apis/uc.api.content.halo.run/v1alpha1/posts/{name}/unpublish": {
|
"/apis/uc.api.content.halo.run/v1alpha1/posts/{name}/unpublish": {
|
||||||
"put": {
|
"put": {
|
||||||
"description": "Unpublish my post.",
|
"description": "Unpublish my post.",
|
||||||
|
|
|
@ -50,4 +50,6 @@ public interface PostService {
|
||||||
Mono<Post> revertToSpecifiedSnapshot(String postName, String snapshotName);
|
Mono<Post> revertToSpecifiedSnapshot(String postName, String snapshotName);
|
||||||
|
|
||||||
Mono<ContentWrapper> deleteContent(String postName, String snapshotName);
|
Mono<ContentWrapper> deleteContent(String postName, String snapshotName);
|
||||||
|
|
||||||
|
Mono<Post> recycleBy(String postName, String username);
|
||||||
}
|
}
|
||||||
|
|
|
@ -379,6 +379,15 @@ public class PostServiceImpl extends AbstractContentService implements PostServi
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mono<Post> recycleBy(String postName, String username) {
|
||||||
|
return getByUsername(postName, username)
|
||||||
|
.flatMap(post -> updatePostWithRetry(post, record -> {
|
||||||
|
record.getSpec().setDeleted(true);
|
||||||
|
return record;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
private Mono<Post> updatePostWithRetry(Post post, UnaryOperator<Post> func) {
|
private Mono<Post> updatePostWithRetry(Post post, UnaryOperator<Post> func) {
|
||||||
return client.update(func.apply(post))
|
return client.update(func.apply(post))
|
||||||
.onErrorResume(OptimisticLockingFailureException.class,
|
.onErrorResume(OptimisticLockingFailureException.class,
|
||||||
|
|
|
@ -124,13 +124,27 @@ public class UcPostEndpoint implements CustomEndpoint {
|
||||||
.operationId("UnpublishMyPost")
|
.operationId("UnpublishMyPost")
|
||||||
.description("Unpublish my post.")
|
.description("Unpublish my post.")
|
||||||
.parameter(namePathParam)
|
.parameter(namePathParam)
|
||||||
.response(responseBuilder().implementation(Post.class)))
|
.response(responseBuilder().implementation(Post.class))
|
||||||
|
)
|
||||||
|
.DELETE("/{name}/recycle", this::recycleMyPost, builder -> builder.tag(tag)
|
||||||
|
.operationId("RecycleMyPost")
|
||||||
|
.description("Move my post to recycle bin.")
|
||||||
|
.parameter(namePathParam)
|
||||||
|
.response(responseBuilder().implementation(Post.class))
|
||||||
|
)
|
||||||
.build(),
|
.build(),
|
||||||
builder -> {
|
builder -> {
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Mono<ServerResponse> recycleMyPost(ServerRequest request) {
|
||||||
|
final var name = request.pathVariable("name");
|
||||||
|
return getCurrentUser()
|
||||||
|
.flatMap(username -> postService.recycleBy(name, username))
|
||||||
|
.flatMap(post -> ServerResponse.ok().bodyValue(post));
|
||||||
|
}
|
||||||
|
|
||||||
private Mono<ServerResponse> getMyPostDraft(ServerRequest request) {
|
private Mono<ServerResponse> getMyPostDraft(ServerRequest request) {
|
||||||
var name = request.pathVariable("name");
|
var name = request.pathVariable("name");
|
||||||
var patched = request.queryParam("patched").map(Boolean::valueOf).orElse(false);
|
var patched = request.queryParam("patched").map(Boolean::valueOf).orElse(false);
|
||||||
|
|
|
@ -39,7 +39,8 @@ metadata:
|
||||||
# Currently, yaml definition does not support i18n, please see https://github.com/halo-dev/halo/issues/3573
|
# Currently, yaml definition does not support i18n, please see https://github.com/halo-dev/halo/issues/3573
|
||||||
rbac.authorization.halo.run/display-name: "Post Author"
|
rbac.authorization.halo.run/display-name: "Post Author"
|
||||||
rbac.authorization.halo.run/dependencies: |
|
rbac.authorization.halo.run/dependencies: |
|
||||||
[ "role-template-post-contributor", "role-template-post-publisher", "role-template-post-attachment-manager" ]
|
[ "role-template-post-contributor", "role-template-post-publisher", "role-template-recycle-my-post",
|
||||||
|
"role-template-post-attachment-manager" ]
|
||||||
rules: [ ]
|
rules: [ ]
|
||||||
|
|
||||||
---
|
---
|
||||||
|
@ -98,6 +99,23 @@ rules:
|
||||||
resources: [ "posts/publish", "posts/unpublish" ]
|
resources: [ "posts/publish", "posts/unpublish" ]
|
||||||
verbs: [ "update" ]
|
verbs: [ "update" ]
|
||||||
|
|
||||||
|
---
|
||||||
|
apiVersion: v1alpha1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: role-template-recycle-my-post
|
||||||
|
labels:
|
||||||
|
halo.run/role-template: "true"
|
||||||
|
annotations:
|
||||||
|
rbac.authorization.halo.run/module: "Posts Management"
|
||||||
|
rbac.authorization.halo.run/display-name: "Recycle My Post"
|
||||||
|
rbac.authorization.halo.run/ui-permissions: |
|
||||||
|
[ "uc:posts:recycle" ]
|
||||||
|
rules:
|
||||||
|
- apiGroups: [ "uc.api.content.halo.run" ]
|
||||||
|
resources: [ "posts/recycle" ]
|
||||||
|
verbs: [ "delete" ]
|
||||||
|
|
||||||
---
|
---
|
||||||
apiVersion: v1alpha1
|
apiVersion: v1alpha1
|
||||||
kind: Role
|
kind: Role
|
||||||
|
|
|
@ -270,6 +270,47 @@ export const PostV1alpha1UcApiAxiosParamCreator = function (configuration?: Conf
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
|
|
||||||
|
return {
|
||||||
|
url: toPathString(localVarUrlObj),
|
||||||
|
options: localVarRequestOptions,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Move my post to recycle bin.
|
||||||
|
* @param {string} name Post name
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
recycleMyPost: async (name: string, options: RawAxiosRequestConfig = {}): Promise<RequestArgs> => {
|
||||||
|
// verify required parameter 'name' is not null or undefined
|
||||||
|
assertParamExists('recycleMyPost', 'name', name)
|
||||||
|
const localVarPath = `/apis/uc.api.content.halo.run/v1alpha1/posts/{name}/recycle`
|
||||||
|
.replace(`{${"name"}}`, encodeURIComponent(String(name)));
|
||||||
|
// use dummy base URL string because the URL constructor only accepts absolute URLs.
|
||||||
|
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
|
||||||
|
let baseOptions;
|
||||||
|
if (configuration) {
|
||||||
|
baseOptions = configuration.baseOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
const localVarRequestOptions = { method: 'DELETE', ...baseOptions, ...options};
|
||||||
|
const localVarHeaderParameter = {} as any;
|
||||||
|
const localVarQueryParameter = {} as any;
|
||||||
|
|
||||||
|
// authentication basicAuth required
|
||||||
|
// http basic authentication required
|
||||||
|
setBasicAuthToObject(localVarRequestOptions, configuration)
|
||||||
|
|
||||||
|
// authentication bearerAuth required
|
||||||
|
// http bearer authentication required
|
||||||
|
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
|
||||||
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
|
||||||
|
@ -488,6 +529,18 @@ export const PostV1alpha1UcApiFp = function(configuration?: Configuration) {
|
||||||
const localVarOperationServerBasePath = operationServerMap['PostV1alpha1UcApi.publishMyPost']?.[localVarOperationServerIndex]?.url;
|
const localVarOperationServerBasePath = operationServerMap['PostV1alpha1UcApi.publishMyPost']?.[localVarOperationServerIndex]?.url;
|
||||||
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Move my post to recycle bin.
|
||||||
|
* @param {string} name Post name
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
async recycleMyPost(name: string, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<Post>> {
|
||||||
|
const localVarAxiosArgs = await localVarAxiosParamCreator.recycleMyPost(name, options);
|
||||||
|
const localVarOperationServerIndex = configuration?.serverIndex ?? 0;
|
||||||
|
const localVarOperationServerBasePath = operationServerMap['PostV1alpha1UcApi.recycleMyPost']?.[localVarOperationServerIndex]?.url;
|
||||||
|
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, localVarOperationServerBasePath || basePath);
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Unpublish my post.
|
* Unpublish my post.
|
||||||
* @param {string} name Post name
|
* @param {string} name Post name
|
||||||
|
@ -581,6 +634,15 @@ export const PostV1alpha1UcApiFactory = function (configuration?: Configuration,
|
||||||
publishMyPost(requestParameters: PostV1alpha1UcApiPublishMyPostRequest, options?: RawAxiosRequestConfig): AxiosPromise<Post> {
|
publishMyPost(requestParameters: PostV1alpha1UcApiPublishMyPostRequest, options?: RawAxiosRequestConfig): AxiosPromise<Post> {
|
||||||
return localVarFp.publishMyPost(requestParameters.name, options).then((request) => request(axios, basePath));
|
return localVarFp.publishMyPost(requestParameters.name, options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
|
/**
|
||||||
|
* Move my post to recycle bin.
|
||||||
|
* @param {PostV1alpha1UcApiRecycleMyPostRequest} requestParameters Request parameters.
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
*/
|
||||||
|
recycleMyPost(requestParameters: PostV1alpha1UcApiRecycleMyPostRequest, options?: RawAxiosRequestConfig): AxiosPromise<Post> {
|
||||||
|
return localVarFp.recycleMyPost(requestParameters.name, options).then((request) => request(axios, basePath));
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Unpublish my post.
|
* Unpublish my post.
|
||||||
* @param {PostV1alpha1UcApiUnpublishMyPostRequest} requestParameters Request parameters.
|
* @param {PostV1alpha1UcApiUnpublishMyPostRequest} requestParameters Request parameters.
|
||||||
|
@ -737,6 +799,20 @@ export interface PostV1alpha1UcApiPublishMyPostRequest {
|
||||||
readonly name: string
|
readonly name: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request parameters for recycleMyPost operation in PostV1alpha1UcApi.
|
||||||
|
* @export
|
||||||
|
* @interface PostV1alpha1UcApiRecycleMyPostRequest
|
||||||
|
*/
|
||||||
|
export interface PostV1alpha1UcApiRecycleMyPostRequest {
|
||||||
|
/**
|
||||||
|
* Post name
|
||||||
|
* @type {string}
|
||||||
|
* @memberof PostV1alpha1UcApiRecycleMyPost
|
||||||
|
*/
|
||||||
|
readonly name: string
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request parameters for unpublishMyPost operation in PostV1alpha1UcApi.
|
* Request parameters for unpublishMyPost operation in PostV1alpha1UcApi.
|
||||||
* @export
|
* @export
|
||||||
|
@ -855,6 +931,17 @@ export class PostV1alpha1UcApi extends BaseAPI {
|
||||||
return PostV1alpha1UcApiFp(this.configuration).publishMyPost(requestParameters.name, options).then((request) => request(this.axios, this.basePath));
|
return PostV1alpha1UcApiFp(this.configuration).publishMyPost(requestParameters.name, options).then((request) => request(this.axios, this.basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Move my post to recycle bin.
|
||||||
|
* @param {PostV1alpha1UcApiRecycleMyPostRequest} requestParameters Request parameters.
|
||||||
|
* @param {*} [options] Override http request option.
|
||||||
|
* @throws {RequiredError}
|
||||||
|
* @memberof PostV1alpha1UcApi
|
||||||
|
*/
|
||||||
|
public recycleMyPost(requestParameters: PostV1alpha1UcApiRecycleMyPostRequest, options?: RawAxiosRequestConfig) {
|
||||||
|
return PostV1alpha1UcApiFp(this.configuration).recycleMyPost(requestParameters.name, options).then((request) => request(this.axios, this.basePath));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unpublish my post.
|
* Unpublish my post.
|
||||||
* @param {PostV1alpha1UcApiUnpublishMyPostRequest} requestParameters Request parameters.
|
* @param {PostV1alpha1UcApiUnpublishMyPostRequest} requestParameters Request parameters.
|
||||||
|
|
|
@ -1517,6 +1517,7 @@ core:
|
||||||
Post Author: Allows you to manage your own posts
|
Post Author: Allows you to manage your own posts
|
||||||
Post Contributor: Contributions allowed
|
Post Contributor: Contributions allowed
|
||||||
Post Publisher: Allow to publish own posts
|
Post Publisher: Allow to publish own posts
|
||||||
|
Recycle My Post: Recycle My Post
|
||||||
components:
|
components:
|
||||||
submit_button:
|
submit_button:
|
||||||
computed_text: "{text} ({shortcut})"
|
computed_text: "{text} ({shortcut})"
|
||||||
|
@ -1791,6 +1792,9 @@ core:
|
||||||
cancel_publish:
|
cancel_publish:
|
||||||
description: Are you sure you want to cancel publishing?
|
description: Are you sure you want to cancel publishing?
|
||||||
title: Cancel publish
|
title: Cancel publish
|
||||||
|
delete:
|
||||||
|
title: Delete post
|
||||||
|
description: This action will move the post to the recycle bin, where it will be managed by the site administrator.
|
||||||
publish_modal:
|
publish_modal:
|
||||||
title: Publish post
|
title: Publish post
|
||||||
setting_modal:
|
setting_modal:
|
||||||
|
|
|
@ -257,6 +257,9 @@ core:
|
||||||
cancel_publish:
|
cancel_publish:
|
||||||
title: 取消发布
|
title: 取消发布
|
||||||
description: 确定要取消发布吗?
|
description: 确定要取消发布吗?
|
||||||
|
delete:
|
||||||
|
title: 删除文章
|
||||||
|
description: 该操作会将文章放入回收站,后续由网站管理员进行管理。
|
||||||
post_editor:
|
post_editor:
|
||||||
title: 文章编辑
|
title: 文章编辑
|
||||||
untitled: 未命名文章
|
untitled: 未命名文章
|
||||||
|
@ -1415,6 +1418,7 @@ core:
|
||||||
Post Author: 允许管理自己的文章
|
Post Author: 允许管理自己的文章
|
||||||
Post Attachment Manager: 允许在文章中上传图片
|
Post Attachment Manager: 允许在文章中上传图片
|
||||||
Post Publisher: 允许发布自己的文章
|
Post Publisher: 允许发布自己的文章
|
||||||
|
Recycle My Post: 允许删除自己的文章
|
||||||
components:
|
components:
|
||||||
submit_button:
|
submit_button:
|
||||||
computed_text: "{text}({shortcut})"
|
computed_text: "{text}({shortcut})"
|
||||||
|
|
|
@ -1394,6 +1394,7 @@ core:
|
||||||
Post Author: 允許管理自己的文章
|
Post Author: 允許管理自己的文章
|
||||||
Post Contributor: 允許投稿
|
Post Contributor: 允許投稿
|
||||||
Post Publisher: 允許發布自己的文章
|
Post Publisher: 允許發布自己的文章
|
||||||
|
Recycle My Post: 允許刪除自己的文章
|
||||||
components:
|
components:
|
||||||
submit_button:
|
submit_button:
|
||||||
computed_text: "{text}({shortcut})"
|
computed_text: "{text}({shortcut})"
|
||||||
|
@ -1659,6 +1660,9 @@ core:
|
||||||
cancel_publish:
|
cancel_publish:
|
||||||
description: 確定要取消發布嗎?
|
description: 確定要取消發布嗎?
|
||||||
title: 取消發布
|
title: 取消發布
|
||||||
|
delete:
|
||||||
|
title: 刪除文章
|
||||||
|
description: 該操作會將文章放入回收站,後續由網站管理員進行管理。
|
||||||
publish_modal:
|
publish_modal:
|
||||||
title: 發布文章
|
title: 發布文章
|
||||||
setting_modal:
|
setting_modal:
|
||||||
|
|
|
@ -92,6 +92,25 @@ function handleUnpublish() {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleDelete() {
|
||||||
|
Dialog.warning({
|
||||||
|
title: t("core.uc_post.operations.delete.title"),
|
||||||
|
description: t("core.uc_post.operations.delete.description"),
|
||||||
|
confirmType: "danger",
|
||||||
|
confirmText: t("core.common.buttons.confirm"),
|
||||||
|
cancelText: t("core.common.buttons.cancel"),
|
||||||
|
async onConfirm() {
|
||||||
|
await ucApiClient.content.post.recycleMyPost({
|
||||||
|
name: props.post.post.metadata.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
Toast.success(t("core.common.toast.delete_success"));
|
||||||
|
|
||||||
|
queryClient.invalidateQueries({ queryKey: ["my-posts"] });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
@ -246,11 +265,16 @@ function handleUnpublish() {
|
||||||
{{ $t("core.common.buttons.edit") }}
|
{{ $t("core.common.buttons.edit") }}
|
||||||
</VDropdownItem>
|
</VDropdownItem>
|
||||||
<HasPermission v-if="!isPublished" :permissions="['uc:posts:publish']">
|
<HasPermission v-if="!isPublished" :permissions="['uc:posts:publish']">
|
||||||
<VDropdownDivider />
|
|
||||||
<VDropdownItem type="danger" @click="handleUnpublish">
|
<VDropdownItem type="danger" @click="handleUnpublish">
|
||||||
{{ $t("core.common.buttons.cancel_publish") }}
|
{{ $t("core.common.buttons.cancel_publish") }}
|
||||||
</VDropdownItem>
|
</VDropdownItem>
|
||||||
</HasPermission>
|
</HasPermission>
|
||||||
|
<HasPermission :permissions="['uc:posts:recycle']">
|
||||||
|
<VDropdownDivider />
|
||||||
|
<VDropdownItem type="danger" @click="handleDelete">
|
||||||
|
{{ $t("core.common.buttons.delete") }}
|
||||||
|
</VDropdownItem>
|
||||||
|
</HasPermission>
|
||||||
</template>
|
</template>
|
||||||
</VEntity>
|
</VEntity>
|
||||||
</template>
|
</template>
|
||||||
|
|
Loading…
Reference in New Issue