Selaa lähdekoodia

:construction: edit done

visuddhinanda 3 vuotta sitten
vanhempi
sitoutus
44baee3dd9

+ 42 - 0
dashboard/src/components/api/Article.ts

@@ -44,3 +44,45 @@ export interface IAnthologyStudioListDataApiResponse {
 	count: number;
 	studio: IStudioApiResponse;
 }
+
+export interface IArticleDataRequest {
+	uid: string;
+	title: string;
+	subtitle: string;
+	summary: string;
+	content: string;
+	content_type: string;
+	status: number;
+	lang: string;
+}
+export interface IArticleDataResponse {
+	uid: string;
+	title: string;
+	subtitle: string;
+	summary: string;
+	content: string;
+	content_type: string;
+	status: number;
+	lang: string;
+	created_at: string;
+	updated_at: string;
+}
+export interface IArticleResponse {
+	ok: boolean;
+	message: string;
+	data: IArticleDataResponse;
+}
+export interface IArticleListResponse {
+	ok: boolean;
+	message: string;
+	data: {
+		rows: IArticleDataResponse[];
+		count: number;
+	};
+}
+
+export interface IArticleCreateRequest {
+	title: string;
+	lang: string;
+	studio: string;
+}

+ 36 - 9
dashboard/src/pages/studio/anthology/edit.tsx

@@ -1,7 +1,12 @@
 import { useParams } from "react-router-dom";
 import { useIntl } from "react-intl";
 
-import { ProForm, ProFormText, ProFormSelect, ProFormTextArea } from "@ant-design/pro-components";
+import {
+	ProForm,
+	ProFormText,
+	ProFormSelect,
+	ProFormTextArea,
+} from "@ant-design/pro-components";
 import { message } from "antd";
 
 import EditableTree from "../../../components/studio/EditableTree";
@@ -47,7 +52,9 @@ const Widget = () => {
 	return (
 		<>
 			<h2>
-				studio/{studioname}/{intl.formatMessage({ id: "columns.studio.anthology.title" })}/anthology/
+				studio/{studioname}/
+				{intl.formatMessage({ id: "columns.studio.anthology.title" })}
+				/anthology/
 				{anthology_id}
 			</h2>
 
@@ -56,7 +63,9 @@ const Widget = () => {
 					// TODO
 					values.studio = "aaaa";
 					console.log(values);
-					message.success(intl.formatMessage({ id: "flashes.success" }));
+					message.success(
+						intl.formatMessage({ id: "flashes.success" })
+					);
 				}}
 			>
 				<ProForm.Group>
@@ -64,11 +73,15 @@ const Widget = () => {
 						width="md"
 						name="title"
 						required
-						label={intl.formatMessage({ id: "forms.fields.title.label" })}
+						label={intl.formatMessage({
+							id: "forms.fields.title.label",
+						})}
 						rules={[
 							{
 								required: true,
-								message: intl.formatMessage({ id: "forms.create.message.no.title" }),
+								message: intl.formatMessage({
+									id: "forms.message.title.required",
+								}),
 							},
 						]}
 					/>
@@ -77,11 +90,18 @@ const Widget = () => {
 					<ProFormText
 						width="md"
 						name="subtitle"
-						label={intl.formatMessage({ id: "forms.fields.subtitle.label" })}
+						label={intl.formatMessage({
+							id: "forms.fields.subtitle.label",
+						})}
 					/>
 				</ProForm.Group>
 				<ProForm.Group>
-					<ProFormTextArea name="summary" label={intl.formatMessage({ id: "forms.fields.summary.label" })} />
+					<ProFormTextArea
+						name="summary"
+						label={intl.formatMessage({
+							id: "forms.fields.summary.label",
+						})}
+					/>
 				</ProForm.Group>
 				<ProForm.Group>
 					<ProFormSelect
@@ -95,7 +115,9 @@ const Widget = () => {
 						rules={[
 							{
 								required: true,
-								message: intl.formatMessage({ id: "forms.create.message.no.lang" }),
+								message: intl.formatMessage({
+									id: "forms.create.message.no.lang",
+								}),
 							},
 						]}
 						label={intl.formatMessage({ id: "channel.lang" })}
@@ -103,7 +125,12 @@ const Widget = () => {
 				</ProForm.Group>
 
 				<ProForm.Group>
-					<ProFormTextArea name="toc" label={intl.formatMessage({ id: "forms.fields.content.label" })} />
+					<ProFormTextArea
+						name="toc"
+						label={intl.formatMessage({
+							id: "forms.fields.content.label",
+						})}
+					/>
 				</ProForm.Group>
 			</ProForm>
 

+ 86 - 29
dashboard/src/pages/studio/article/edit.tsx

@@ -1,15 +1,29 @@
 import { useParams } from "react-router-dom";
 import { useIntl } from "react-intl";
-import { ProForm, ProFormText, ProFormSelect, ProFormTextArea } from "@ant-design/pro-components";
+import {
+	ProForm,
+	ProFormText,
+	ProFormSelect,
+	ProFormTextArea,
+} from "@ant-design/pro-components";
 import { message } from "antd";
+import { get, put } from "../../../request";
+import {
+	IArticleDataRequest,
+	IArticleResponse,
+} from "../../../components/api/Article";
+import LangSelect from "../../../components/studio/LangSelect";
+import PublicitySelect from "../../../components/studio/PublicitySelect";
 
 interface IFormData {
+	uid: string;
 	title: string;
 	subtitle: string;
 	summary: string;
-	lang: string;
-	studio: string;
 	content: string;
+	content_type: string;
+	status: number;
+	lang: string;
 }
 
 const Widget = () => {
@@ -18,16 +32,54 @@ const Widget = () => {
 	return (
 		<>
 			<h2>
-				studio/{studioname}/{intl.formatMessage({ id: "columns.studio.article.title" })}/edit/
+				studio/{studioname}/
+				{intl.formatMessage({ id: "columns.studio.article.title" })}
+				/edit/
 				{articleid}
 			</h2>
 
 			<ProForm<IFormData>
 				onFinish={async (values: IFormData) => {
 					// TODO
-					values.studio = "aaaa";
-					console.log(values);
-					message.success(intl.formatMessage({ id: "flashes.success" }));
+
+					const request = {
+						uid: articleid ? articleid : "",
+						title: values.title,
+						subtitle: values.subtitle,
+						summary: values.summary,
+						content: values.content,
+						content_type: "markdown",
+						status: values.status,
+						lang: values.lang,
+					};
+					console.log(request);
+					const res = await put<
+						IArticleDataRequest,
+						IArticleResponse
+					>(`/v2/article/${articleid}`, request);
+					console.log(res);
+					if (res.ok) {
+						message.success(
+							intl.formatMessage({ id: "flashes.success" })
+						);
+					} else {
+						message.error(res.message);
+					}
+				}}
+				request={async () => {
+					const res = await get<IArticleResponse>(
+						`/v2/article/${articleid}`
+					);
+					return {
+						uid: res.data.uid,
+						title: res.data.title,
+						subtitle: res.data.subtitle,
+						summary: res.data.summary,
+						content: res.data.content,
+						content_type: res.data.content_type,
+						lang: res.data.lang,
+						status: res.data.status,
+					};
 				}}
 			>
 				<ProForm.Group>
@@ -35,11 +87,15 @@ const Widget = () => {
 						width="md"
 						name="title"
 						required
-						label={intl.formatMessage({ id: "forms.fields.title.label" })}
+						label={intl.formatMessage({
+							id: "forms.fields.title.label",
+						})}
 						rules={[
 							{
 								required: true,
-								message: intl.formatMessage({ id: "forms.create.message.no.title" }),
+								message: intl.formatMessage({
+									id: "forms.message.title.required",
+								}),
 							},
 						]}
 					/>
@@ -48,33 +104,34 @@ const Widget = () => {
 					<ProFormText
 						width="md"
 						name="subtitle"
-						label={intl.formatMessage({ id: "forms.fields.subtitle.label" })}
+						label={intl.formatMessage({
+							id: "forms.fields.subtitle.label",
+						})}
 					/>
 				</ProForm.Group>
 				<ProForm.Group>
-					<ProFormTextArea name="summary" label={intl.formatMessage({ id: "forms.fields.summary.label" })} />
-				</ProForm.Group>
-				<ProForm.Group>
-					<ProFormSelect
-						options={[
-							{ value: "zh-Hans", label: "简体中文" },
-							{ value: "zh-Hant", label: "繁体中文" },
-							{ value: "en-US", label: "English" },
-						]}
+					<ProFormTextArea
+						name="summary"
 						width="md"
-						name="lang"
-						rules={[
-							{
-								required: true,
-								message: intl.formatMessage({ id: "forms.create.message.no.lang" }),
-							},
-						]}
-						label={intl.formatMessage({ id: "channel.lang" })}
+						label={intl.formatMessage({
+							id: "forms.fields.summary.label",
+						})}
 					/>
 				</ProForm.Group>
-
 				<ProForm.Group>
-					<ProFormTextArea name="content" label={intl.formatMessage({ id: "forms.fields.content.label" })} />
+					<LangSelect />
+				</ProForm.Group>
+				<ProForm.Group>
+					<PublicitySelect />
+				</ProForm.Group>
+				<ProForm.Group>
+					<ProFormTextArea
+						name="content"
+						width="md"
+						label={intl.formatMessage({
+							id: "forms.fields.content.label",
+						})}
+					/>
 				</ProForm.Group>
 			</ProForm>
 		</>

+ 267 - 88
dashboard/src/pages/studio/article/list.tsx

@@ -2,48 +2,62 @@ import { useParams, Link } from "react-router-dom";
 import { useIntl } from "react-intl";
 import { useState } from "react";
 
-import { Space, Layout, Breadcrumb, Button, Tag, Popover } from "antd";
-import { ProList } from "@ant-design/pro-components";
-import { CheckCircleOutlined, PlusOutlined } from "@ant-design/icons";
+import {
+	Space,
+	Layout,
+	Breadcrumb,
+	Button,
+	Popover,
+	Dropdown,
+	MenuProps,
+	Menu,
+	Table,
+} from "antd";
+import { ProTable } from "@ant-design/pro-components";
+import { PlusOutlined, SearchOutlined } from "@ant-design/icons";
 
 import ArticleCreate from "../../../components/studio/article/ArticleCreate";
+import { get } from "../../../request";
+import { IArticleListResponse } from "../../../components/api/Article";
+const onMenuClick: MenuProps["onClick"] = (e) => {
+	console.log("click", e);
+};
 
-const defaultData = [
-	{
-		id: "1",
-		title: "Nissaya Book List",
-		name: "Nissaya Book List",
-		tag: [{ title: "公开", color: "success", icon: <CheckCircleOutlined /> }],
-		description: "文章概要",
-	},
-	{
-		id: "2",
-		title: "初级巴利语入门",
-		name: "初级巴利语入门",
-		tag: [{ title: "公开", color: "success", icon: <CheckCircleOutlined /> }],
-		description: "文章概要",
-	},
-	{
-		id: "3",
-		title: "何人有资格接受卡提那衣",
-		name: "何人有资格接受卡提那衣",
-		tag: [{ title: "公开", color: "success", icon: <CheckCircleOutlined /> }],
-		description: "文章概要",
-	},
-	{
-		id: "4",
-		title: "Adhiṭṭhana 定名/决意",
-		name: "Adhiṭṭhana 定名/决意",
-		tag: [{ title: "公开", color: "success", icon: <CheckCircleOutlined /> }],
-		description: "文章概要",
-	},
-];
-type DataItem = typeof defaultData[number];
-
+const menu = (
+	<Menu
+		onClick={onMenuClick}
+		items={[
+			{
+				key: "1",
+				label: "在藏经阁中打开",
+				icon: <SearchOutlined />,
+			},
+			{
+				key: "2",
+				label: "分享",
+				icon: <SearchOutlined />,
+			},
+			{
+				key: "3",
+				label: "删除",
+				icon: <SearchOutlined />,
+			},
+		]}
+	/>
+);
+interface DataItem {
+	sn: number;
+	id: string;
+	title: string;
+	subtitle: string;
+	summary: string;
+	publicity: number;
+	createdAt: number;
+}
 const Widget = () => {
 	const intl = useIntl(); //i18n
 	const { studioname } = useParams(); //url 参数
-	const [dataSource, setDataSource] = useState<DataItem[]>(defaultData);
+
 	const articleCreate = <ArticleCreate studio={studioname} />;
 	const linkRead = `/article/show/12345`;
 	const linkStudio = `/studio/${studioname}`;
@@ -52,83 +66,248 @@ const Widget = () => {
 		<>
 			<Breadcrumb>
 				<Breadcrumb.Item>
-					<Link to={linkStudio}>{intl.formatMessage({ id: "columns.studio.title" })}</Link>
+					<Link to={linkStudio}>
+						{intl.formatMessage({ id: "columns.studio.title" })}
+					</Link>
+				</Breadcrumb.Item>
+				<Breadcrumb.Item>
+					{intl.formatMessage({
+						id: "columns.studio.collaboration.title",
+					})}
+				</Breadcrumb.Item>
+				<Breadcrumb.Item>
+					{intl.formatMessage({ id: "columns.studio.article.title" })}
 				</Breadcrumb.Item>
-				<Breadcrumb.Item>{intl.formatMessage({ id: "columns.studio.collaboration.title" })}</Breadcrumb.Item>
-				<Breadcrumb.Item>{intl.formatMessage({ id: "columns.studio.article.title" })}</Breadcrumb.Item>
 			</Breadcrumb>
 			<Layout>
-				<ProList<DataItem>
-					rowKey="id"
-					headerTitle="文章"
-					dataSource={dataSource}
-					showActions="hover"
-					editable={{
-						onSave: async (key, record, originRow) => {
-							console.log(key, record, originRow);
-							return true;
+				<ProTable<DataItem>
+					columns={[
+						{
+							title: intl.formatMessage({
+								id: "dict.fields.sn.label",
+							}),
+							dataIndex: "sn",
+							key: "sn",
+							width: 50,
+							search: false,
 						},
-					}}
-					onDataSourceChange={setDataSource}
-					metas={{
-						title: {
-							dataIndex: "name",
+						{
+							title: intl.formatMessage({
+								id: "forms.fields.title.label",
+							}),
+							dataIndex: "title",
+							key: "title",
+							tip: "过长会自动收缩",
+							ellipsis: true,
 							render: (text, row, index, action) => {
-								const linkEdit = `/studio/${studioname}/article/edit/${row.id}`;
-
-								return <Link to={linkEdit}>{row.name}</Link>;
+								return (
+									<Link
+										to={`/studio/${studioname}/article/${row.id}/edit`}
+									>
+										{row.title}
+									</Link>
+								);
 							},
 						},
-						description: {
-							dataIndex: "description",
-							search: false,
+						{
+							title: intl.formatMessage({
+								id: "forms.fields.subtitle.label",
+							}),
+							dataIndex: "subtitle",
+							key: "subtitle",
+							tip: "过长会自动收缩",
+							ellipsis: true,
 						},
-						content: {
-							dataIndex: "content",
-							editable: false,
-							search: false,
+						{
+							title: intl.formatMessage({
+								id: "forms.fields.summary.label",
+							}),
+							dataIndex: "summary",
+							key: "summary",
+							tip: "过长会自动收缩",
+							ellipsis: true,
 						},
-						subTitle: {
+
+						{
+							title: intl.formatMessage({
+								id: "forms.fields.publicity.label",
+							}),
+							dataIndex: "publicity",
+							key: "publicity",
+							width: 100,
 							search: false,
-							render: (text, row, index, action) => {
-								const showtag = row.tag.map((item, key) => {
-									return (
-										<Tag color={item.color} icon={item.icon}>
-											{item.title}
-										</Tag>
-									);
-								});
-								return <Space size={0}>{showtag}</Space>;
+							filters: true,
+							onFilter: true,
+							valueEnum: {
+								all: {
+									text: intl.formatMessage({
+										id: "tables.publicity.all",
+									}),
+									status: "Default",
+								},
+								0: {
+									text: intl.formatMessage({
+										id: "tables.publicity.disable",
+									}),
+									status: "Default",
+								},
+								10: {
+									text: intl.formatMessage({
+										id: "tables.publicity.private",
+									}),
+									status: "Processing",
+								},
+								20: {
+									text: intl.formatMessage({
+										id: "tables.publicity.public.bylink",
+									}),
+									status: "Processing",
+								},
+								30: {
+									text: intl.formatMessage({
+										id: "tables.publicity.public",
+									}),
+									status: "Success",
+								},
+								40: {
+									text: intl.formatMessage({
+										id: "tables.publicity.public.edit",
+									}),
+									status: "Success",
+								},
 							},
 						},
-						actions: {
+						{
+							title: intl.formatMessage({
+								id: "forms.fields.created-at.label",
+							}),
+							key: "created-at",
+							width: 100,
+							search: false,
+							dataIndex: "createdAt",
+							valueType: "date",
+							sorter: (a, b) => a.createdAt - b.createdAt,
+						},
+						{
+							title: intl.formatMessage({ id: "buttons.option" }),
+							key: "option",
+							width: 120,
+							valueType: "option",
 							render: (text, row, index, action) => {
-								const linkEdit = `/studio/${studioname}/article/edit/${row.id}`;
-
 								return [
-									<Link to={linkEdit}>编辑</Link>,
-									<Link to={linkRead}>阅读</Link>,
-									<Button onClick={() => {}} key="link" danger>
-										删除
-									</Button>,
-									<Button onClick={() => {}} key="link">
-										分享
-									</Button>,
+									<Dropdown.Button
+										key={index}
+										type="link"
+										overlay={menu}
+									>
+										<Link
+											to={`/studio/${studioname}/article/${row.id}/edit`}
+										>
+											{intl.formatMessage({
+												id: "buttons.edit",
+											})}
+										</Link>
+									</Dropdown.Button>,
 								];
 							},
 						},
+					]}
+					rowSelection={{
+						// 自定义选择项参考: https://ant.design/components/table-cn/#components-table-demo-row-selection-custom
+						// 注释该行则默认不显示下拉选项
+						selections: [
+							Table.SELECTION_ALL,
+							Table.SELECTION_INVERT,
+						],
+					}}
+					tableAlertRender={({
+						selectedRowKeys,
+						selectedRows,
+						onCleanSelected,
+					}) => (
+						<Space size={24}>
+							<span>
+								{intl.formatMessage({ id: "buttons.selected" })}
+								{selectedRowKeys.length}
+								<Button
+									type="link"
+									style={{ marginInlineStart: 8 }}
+									onClick={onCleanSelected}
+								>
+									{intl.formatMessage({
+										id: "buttons.unselect",
+									})}
+								</Button>
+							</span>
+						</Space>
+					)}
+					tableAlertOptionRender={() => {
+						return (
+							<Space size={16}>
+								<Button type="link">
+									{intl.formatMessage({
+										id: "buttons.delete.all",
+									})}
+								</Button>
+							</Space>
+						);
 					}}
-					search={{
-						filterType: "light",
+					request={async (params = {}, sorter, filter) => {
+						// TODO
+						console.log(params, sorter, filter);
+						let url = `/v2/article?view=studio&name=${studioname}`;
+						const offset =
+							((params.current ? params.current : 1) - 1) *
+							(params.pageSize ? params.pageSize : 20);
+						url += `&limit=${params.pageSize}&offset=${offset}`;
+						if (typeof params.keyword !== "undefined") {
+							url +=
+								"&search=" +
+								(params.keyword ? params.keyword : "");
+						}
+
+						const res = await get<IArticleListResponse>(url);
+						const items: DataItem[] = res.data.rows.map(
+							(item, id) => {
+								const date = new Date(item.created_at);
+								return {
+									sn: id + 1,
+									id: item.uid,
+									title: item.title,
+									subtitle: item.subtitle,
+									summary: item.summary,
+									publicity: item.status,
+									createdAt: date.getTime(),
+								};
+							}
+						);
+						return {
+							total: res.data.count,
+							succcess: true,
+							data: items,
+						};
 					}}
+					rowKey="id"
 					bordered
 					pagination={{
 						showQuickJumper: true,
 						showSizeChanger: true,
 					}}
+					search={false}
+					options={{
+						search: true,
+					}}
 					toolBarRender={() => [
-						<Popover content={articleCreate} title="new article" placement="bottomRight">
-							<Button key="button" icon={<PlusOutlined />} type="primary">
+						<Popover
+							content={articleCreate}
+							title="Create"
+							placement="bottomRight"
+						>
+							<Button
+								key="button"
+								icon={<PlusOutlined />}
+								type="primary"
+							>
 								{intl.formatMessage({ id: "buttons.create" })}
 							</Button>
 						</Popover>,