Pārlūkot izejas kodu

Merge branch 'master' into development

visuddhinanda 1 gadu atpakaļ
vecāks
revīzija
6fff1dde31

+ 26 - 0
api-v8/app/Http/Api/NotificationApi.php

@@ -0,0 +1,26 @@
+<?php
+namespace App\Http\Api;
+
+use App\Models\Notification;
+use Illuminate\Support\Str;
+
+class NotificationApi{
+    public static function send($data){
+        $insertData = [];
+        foreach ($data as $key => $row) {
+            $insertData[] = [
+                'id' => Str::uuid(),
+                'from' => $row['from'],
+                'to' => $row['to'],
+                'url' => $row['url'],
+                'content' => $row['content'],
+                'res_type' => $row['res_type'],
+                'res_id' => $row['res_id'],
+                'updated_at' => now(),
+                'created_at' => now(),
+            ];
+        }
+        $insert = Notification::insert($insertData);
+        return $insert;
+    }
+}

+ 25 - 0
api-v8/app/Http/Api/WatchApi.php

@@ -0,0 +1,25 @@
+<?php
+namespace App\Http\Api;
+
+use App\Models\Like;
+
+class WatchApi{
+    public static function change($resId,$from,$message){
+        //发送站内信
+        $watches = Like::where('type','watch')
+                    ->where('target_id',$resId)
+                    ->get();
+        $notifications = [];
+        foreach ($watches as $key => $watch) {
+            $notifications[] = [
+                'from' => $from,
+                'to' => $watch->user_id,
+                'url' => $row['url'],
+                'content' => $message,
+                'res_type' => $watch->res_type,
+                'res_id' => $watch['res_id'],
+            ];
+
+        }
+    }
+}

+ 74 - 26
api-v8/app/Http/Controllers/LikeController.php

@@ -4,6 +4,10 @@ namespace App\Http\Controllers;
 
 use App\Models\Like;
 use Illuminate\Http\Request;
+use App\Http\Api\AuthApi;
+use App\Http\Api\UserApi;
+use App\Http\Resources\LikeResource;
+use Illuminate\Support\Str;
 
 class LikeController extends Controller
 {
@@ -18,28 +22,41 @@ class LikeController extends Controller
         switch ($request->get("view")) {
             case 'count':
                 # code...
-                $resulte = Like::where("target_id",$request->get("target_id"))
+                $result = Like::where("target_id",$request->get("target_id"))
                                 ->groupBy("type")
                                 ->select("type")
                                 ->selectRaw("count(*)")
                                 ->get();
-                if(isset($_COOKIE["user_uid"])){
-                    foreach ($resulte as $key => $value) {
-                        # code...
-                        if(Like::where(["target_id"=>$request->get("target_id"),
+                $user = AuthApi::current($request);
+                if($user){
+                    foreach ($result as $key => $value) {
+                        $curr = Like::where(["target_id"=>$request->get("target_id"),
                                         'type'=>$value->type,
-                                        'user_id'=>$_COOKIE["user_uid"]])->exists()){
-                            $resulte[$key]->selected = true;
+                                        'user_id'=>$user["user_uid"]])->first();
+                        if($curr){
+                            $result[$key]->selected = true;
+                            $result[$key]->my_id = $curr->id;
                         }
                     }
                 }
+                return $this->ok($result);
+                break;
+            case 'target':
+                $table = Like::where("target_id",$request->get("target_id"));
                 break;
             default:
                 # code...
                 break;
         }
-
-        return $this->ok($resulte);
+        if($request->has("type")){
+            $table = $table->where('type',$request->get("type"));
+        }
+        $count = $table->count();
+        $result = $table->get();
+        return $this->ok([
+            "rows"=>LikeResource::collection($result),
+            "count"=>$count
+        ]);
     }
 
     /**
@@ -51,14 +68,35 @@ class LikeController extends Controller
     public function store(Request $request)
     {
         //
-        if(!isset($_COOKIE["user_uid"])){
-            return $this->error("no login");
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'));
         }
         $param = $request->all();
-        $param['user_id'] = $_COOKIE["user_uid"];
-        $like = Like::firstOrCreate($param);
-
-        return $this->ok($like);
+        $user_id = $request->get('user_id',$user["user_uid"]);
+        $like = Like::firstOrNew([
+            'type'=>$param['type'],
+            'target_id'=>$param['target_id'],
+            'target_type'=>$param['target_type'],
+            'user_id' =>  $user_id,
+        ],
+        [
+            'id'=>Str::uuid(),
+        ]);
+        $like->save();
+        $output = [
+            'id'=>$like->id,
+            'type'=>$param['type'],
+            'target_id'=>$param['target_id'],
+            'target_type'=>$param['target_type'],
+            'user_id' => $user_id,
+            'count'=>Like::where('target_id',$param['target_id'])
+                        ->where('type',$param['type'])->count(),
+            'selected'=>true,
+            'my_id'=>$like->id,
+            'user' => UserApi::getByUuid($user_id),
+        ];
+        return $this->ok($output);
     }
 
     /**
@@ -90,21 +128,31 @@ class LikeController extends Controller
      * @param  \App\Models\Like  $like
      * @return \Illuminate\Http\Response
      */
-    public function destroy(Like $like)
+    public function destroy(Request $request,Like $like)
     {
         //
-        if(!isset($_COOKIE["user_uid"])){
-            return $this->error("no login");
+        $user = AuthApi::current($request);
+        if(!$user){
+            return $this->error(__('auth.failed'));
         }
-        if($like->user_id==$_COOKIE["user_uid"]){
-            return $this->ok($like->delete());
+        if($like->user_id===$user["user_uid"]){
+            //移除自己
+            $delete = $like->delete();
+            if($delete){
+                $output = [
+                    'type'=>$like['type'],
+                    'count'=>Like::where('target_id',$like['target_id'])
+                                ->where('type',$like['type'])->count(),
+                    'selected'=>false,
+                ];
+                return $this->ok($output);
+            }else{
+                $this->error('未知错误',200,200);
+            }
+
+        }else{
+            return $this->error(_('auth.failed'),403,403);
         }
-        
-        $param = $request->all();
-        $param['user_id'] = $_COOKIE["user_uid"];
-        $like = Like::where($param)->delete();
-        return $this->ok($like);
-        
     }
     public function delete(Request $request){
         if(!isset($_COOKIE["user_uid"])){

+ 32 - 0
api-v8/app/Http/Resources/LikeResource.php

@@ -0,0 +1,32 @@
+<?php
+
+namespace App\Http\Resources;
+
+use Illuminate\Http\Resources\Json\JsonResource;
+use App\Http\Api\UserApi;
+
+class LikeResource extends JsonResource
+{
+    /**
+     * Transform the resource into an array.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @return array|\Illuminate\Contracts\Support\Arrayable|\JsonSerializable
+     */
+    public function toArray($request)
+    {
+        $data = [
+            'id' => $this->id,
+            'type' => $this->type,
+            'target_id' => $this->target_id,
+            'target_type' => $this->target_type,
+            'context' => $this->context,
+            'updated_at' => $this->updated_at,
+            'created_at' => $this->created_at
+        ];
+        if($this->user_id){
+            $data['user'] = UserApi::getByUuid($this->user_id);
+        }
+        return $data;
+    }
+}

+ 4 - 3
api-v8/app/Models/Like.php

@@ -8,8 +8,9 @@ use Illuminate\Database\Eloquent\Model;
 class Like extends Model
 {
     use HasFactory;
+    protected $primaryKey = 'id';
+	protected $casts = [
+		'id' => 'string'
+	];
     protected $fillable = ['type' , 'target_id', 'target_type','user_id','context'];
-    protected $casts = [
-            'id' => 'string'
-        ];
 }

+ 56 - 0
dashboard-v4/dashboard/src/components/api/like.ts

@@ -0,0 +1,56 @@
+import { IUser } from "../auth/User";
+
+export type TLikeType = "like" | "dislike" | "favorite" | "watch";
+export interface ILikeData {
+  id: string;
+  type: TLikeType;
+  target_id: string;
+  target_type?: string;
+  user: IUser;
+  context?: string | null;
+  selected?: boolean;
+  my_id?: string;
+  count?: number;
+  updated_at?: string;
+  created_at?: string;
+}
+export interface ILikeCount {
+  type: TLikeType;
+  selected?: boolean;
+  my_id?: string;
+  count?: number;
+  user: IUser;
+}
+
+export interface ILikeListResponse {
+  ok: boolean;
+  message: string;
+  data: {
+    rows: ILikeData[];
+    count: number;
+  };
+}
+
+export interface ILikeResponse {
+  ok: boolean;
+  message: string;
+  data: ILikeData;
+}
+
+export interface ILikeCountListResponse {
+  ok: boolean;
+  message: string;
+  data: ILikeCount[];
+}
+export interface ILikeCountResponse {
+  ok: boolean;
+  message: string;
+  data: ILikeCount;
+}
+
+export interface ILikeRequest {
+  type: TLikeType;
+  target_id: string;
+  target_type: string;
+  user_id?: string;
+}

+ 22 - 15
dashboard-v4/dashboard/src/components/auth/User.tsx

@@ -18,6 +18,7 @@ interface IWidget {
   showAvatar?: boolean;
   showName?: boolean;
   showUserName?: boolean;
+  hidePopover?: boolean;
 }
 const UserWidget = ({
   nickName,
@@ -26,8 +27,27 @@ const UserWidget = ({
   showAvatar = true,
   showName = true,
   showUserName = false,
+  hidePopover = false,
 }: IWidget) => {
-  return (
+  const inner = (
+    <Space>
+      {showAvatar ? (
+        <Avatar
+          size={"small"}
+          src={avatar}
+          style={{ backgroundColor: getAvatarColor(nickName) }}
+        >
+          {nickName?.slice(0, 2)}
+        </Avatar>
+      ) : undefined}
+      {showName ? <Text>{nickName}</Text> : undefined}
+      {showName && showUserName ? <Text>@</Text> : undefined}
+      {showUserName ? <Text>{userName}</Text> : undefined}
+    </Space>
+  );
+  return hidePopover ? (
+    inner
+  ) : (
     <Popover
       content={
         <div>
@@ -44,20 +64,7 @@ const UserWidget = ({
         </div>
       }
     >
-      <Space>
-        {showAvatar ? (
-          <Avatar
-            size={"small"}
-            src={avatar}
-            style={{ backgroundColor: getAvatarColor(nickName) }}
-          >
-            {nickName?.slice(0, 2)}
-          </Avatar>
-        ) : undefined}
-        {showName ? <Text>{nickName}</Text> : undefined}
-        {showName && showUserName ? <Text>@</Text> : undefined}
-        {showUserName ? <Text>{userName}</Text> : undefined}
-      </Space>
+      {inner}
     </Popover>
   );
 };

+ 136 - 0
dashboard-v4/dashboard/src/components/like/Like.tsx

@@ -0,0 +1,136 @@
+import { useEffect, useState } from "react";
+import { Button, Space, Tooltip } from "antd";
+import {
+  LikeOutlined,
+  LikeFilled,
+  StarOutlined,
+  StarFilled,
+  EyeOutlined,
+  EyeFilled,
+} from "@ant-design/icons";
+
+import { delete_, get, post } from "../../request";
+import {
+  ILikeCount,
+  ILikeCountListResponse,
+  ILikeCountResponse,
+  ILikeRequest,
+  TLikeType,
+} from "../api/like";
+
+interface IWidget {
+  resId?: string;
+  resType?: string;
+}
+const Like = ({ resId, resType }: IWidget) => {
+  const [like, setLike] = useState<ILikeCount>();
+  const [favorite, setFavorite] = useState<ILikeCount>();
+  const [watch, setWatch] = useState<ILikeCount>();
+
+  useEffect(() => {
+    if (!resId) {
+      return;
+    }
+    const url = `/v2/like?view=count&target_id=${resId}`;
+    console.info("api request", url);
+    get<ILikeCountListResponse>(url).then((json) => {
+      console.info("api response", json);
+      if (json.ok) {
+        setLike(json.data.find((value) => value.type === "like"));
+        setFavorite(json.data.find((value) => value.type === "favorite"));
+        setWatch(json.data.find((value) => value.type === "watch"));
+      }
+    });
+  }, [resId]);
+
+  const setStatus = (data: ILikeCount) => {
+    switch (data.type) {
+      case "like":
+        setLike(data);
+        break;
+      case "favorite":
+        setFavorite(data);
+        break;
+      case "watch":
+        setWatch(data);
+        break;
+    }
+  };
+  const add = (type: TLikeType) => {
+    if (!resId || !resType) {
+      return;
+    }
+    const url = `/v2/like`;
+    post<ILikeRequest, ILikeCountResponse>(url, {
+      type: type,
+      target_id: resId,
+      target_type: resType,
+    }).then((json) => {
+      if (json.ok) {
+        setStatus(json.data);
+      }
+    });
+  };
+
+  const remove = (id?: string) => {
+    if (!resId || !resType || !id) {
+      return;
+    }
+    const url = `/v2/like/${id}`;
+    console.info("api request", url);
+    delete_<ILikeCountResponse>(url).then((json) => {
+      console.info("api response", json);
+      if (json.ok) {
+        setStatus(json.data);
+      }
+    });
+  };
+
+  return (
+    <Space>
+      <Button
+        type="text"
+        icon={like?.selected ? <LikeFilled /> : <LikeOutlined />}
+        onClick={() => {
+          if (like?.selected) {
+            remove(like.my_id);
+          } else {
+            add("like");
+          }
+        }}
+      >
+        {like?.count === 0 ? <></> : like?.count}
+      </Button>
+      <Button
+        type="text"
+        icon={favorite?.selected ? <StarFilled /> : <StarOutlined />}
+        onClick={() => {
+          if (favorite?.selected) {
+            remove(favorite.my_id);
+          } else {
+            add("favorite");
+          }
+        }}
+      >
+        {favorite?.count === 0 ? <></> : favorite?.count}
+      </Button>
+      <Tooltip title="关注">
+        <Button
+          type="text"
+          icon={watch?.selected ? <EyeFilled /> : <EyeOutlined />}
+          onClick={() => {
+            if (watch?.selected) {
+              remove(watch.my_id);
+            } else {
+              add("watch");
+            }
+          }}
+        >
+          {watch?.count === 0 ? <></> : watch?.count}
+        </Button>
+      </Tooltip>
+    </Space>
+  );
+};
+
+export default Like;

+ 64 - 0
dashboard-v4/dashboard/src/components/like/LikeAvatar.tsx

@@ -0,0 +1,64 @@
+import { useEffect, useState } from "react";
+
+import { ILikeData, ILikeListResponse, TLikeType } from "../api/like";
+import { get } from "../../request";
+import User from "../auth/User";
+import { Popover, Space } from "antd";
+import WatchList from "./WatchList";
+import { WatchAddButton } from "./WatchAdd";
+
+interface IWidget {
+  resId?: string;
+  resType?: string;
+  type?: TLikeType;
+}
+const LikeAvatar = ({ resId, resType, type }: IWidget) => {
+  const [data, setData] = useState<ILikeData[]>();
+  useEffect(() => {
+    if (!resId) {
+      return;
+    }
+    const url = `/v2/like?view=target&target_id=${resId}&type=${type}`;
+    console.info("api request", url);
+    get<ILikeListResponse>(url).then((json) => {
+      console.info("api response", json);
+      if (json.ok) {
+        setData(json.data.rows);
+      }
+    });
+  }, [resId, type]);
+  return (
+    <Space>
+      <Popover trigger={"click"} content={<WatchList data={data} />}>
+        <div>
+          {data?.map((item, id) => {
+            return (
+              <span
+                key={id}
+                style={{ display: "inline-block", marginRight: -8 }}
+              >
+                <User {...item.user} showName={false} hidePopover />
+              </span>
+            );
+          })}
+        </div>
+      </Popover>
+      <WatchAddButton
+        resId={resId}
+        resType={resType}
+        data={data}
+        onAdd={(user: ILikeData) => {
+          setData((origin) => {
+            if (origin) {
+              return [...origin, user];
+            } else {
+              return [user];
+            }
+          });
+        }}
+      />
+    </Space>
+  );
+};
+
+export default LikeAvatar;

+ 63 - 0
dashboard-v4/dashboard/src/components/like/WatchAdd.tsx

@@ -0,0 +1,63 @@
+import { useRef } from "react";
+import { ProForm, ProFormInstance } from "@ant-design/pro-components";
+import { PlusOutlined } from "@ant-design/icons";
+
+import { ILikeData, ILikeRequest, ILikeResponse } from "../api/like";
+import UserSelect from "../template/UserSelect";
+import { post } from "../../request";
+import { Button, Divider, Popover } from "antd";
+import WatchList from "./WatchList";
+
+interface IWidget {
+  resId?: string;
+  resType?: string;
+  data?: ILikeData[];
+  onAdd?: (user: ILikeData) => void;
+}
+
+export const WatchAddButton = ({ resId, resType, data, onAdd }: IWidget) => {
+  return (
+    <Popover
+      trigger={"click"}
+      content={
+        <WatchAdd resId={resId} resType={resType} data={data} onAdd={onAdd} />
+      }
+    >
+      <Button type="text" icon={<PlusOutlined />} />
+    </Popover>
+  );
+};
+const WatchAdd = ({ resId, resType, data, onAdd }: IWidget) => {
+  const formRef = useRef<ProFormInstance>();
+  return (
+    <div>
+      <ProForm<ILikeRequest>
+        formRef={formRef}
+        onFinish={async (values: ILikeRequest) => {
+          if (!resId || !resType) {
+            console.error("no resId or resType", resId, resType);
+            return;
+          }
+          values.type = "watch";
+          values.target_id = resId;
+          values.target_type = resType;
+          const url = `/v2/like`;
+          console.info("watch add api request", url, values);
+          const add = await post<ILikeRequest, ILikeResponse>(url, values);
+          console.debug("watch add api response", add);
+          if (add.ok) {
+            onAdd && onAdd(add.data);
+          }
+        }}
+      >
+        <ProForm.Group>
+          <UserSelect name="user_id" multiple={false} />
+        </ProForm.Group>
+      </ProForm>
+      <Divider />
+      <WatchList data={data} />
+    </div>
+  );
+};
+
+export default WatchAdd;

+ 25 - 0
dashboard-v4/dashboard/src/components/like/WatchList.tsx

@@ -0,0 +1,25 @@
+import { Button, List } from "antd";
+import { DeleteOutlined } from "@ant-design/icons";
+
+import User from "../auth/User";
+import { ILikeData } from "../api/like";
+
+interface IWidget {
+  data?: ILikeData[];
+}
+const WatchList = ({ data }: IWidget) => {
+  return (
+    <List
+      dataSource={data}
+      renderItem={(item) => (
+        <List.Item
+          extra={[<Button type="text" danger icon={<DeleteOutlined />} />]}
+        >
+          <User {...item.user} />
+        </List.Item>
+      )}
+    />
+  );
+};
+
+export default WatchList;

+ 10 - 2
dashboard-v4/dashboard/src/components/task/TaskEditDrawer.tsx

@@ -1,10 +1,13 @@
-import { Button, Drawer,  } from "antd";
+import { Button, Drawer, Space, Typography } from "antd";
 import { useEffect, useState } from "react";
 
 import { ITaskData } from "../api/task";
 import Task from "./Task";
 import { useIntl } from "react-intl";
 import { fullUrl } from "../../utils";
+import LikeAvatar from "../like/LikeAvatar";
+
+const { Text } = Typography;
 
 interface IWidget {
   taskId?: string;
@@ -41,7 +44,12 @@ const TaskEditDrawer = ({
         onClose={onCloseDrawer}
         open={open}
         destroyOnClose={true}
-        footer={<div>关注</div>}
+        footer={
+          <Space>
+            <Text>关注</Text>
+            <LikeAvatar resId={taskId} resType="task" type="watch" />
+          </Space>
+        }
         extra={
           <Button
             type="link"

+ 0 - 3
dashboard-v4/dashboard/src/components/task/TaskList.tsx

@@ -108,7 +108,6 @@ const TaskList = ({
 }: IWidget) => {
   const intl = useIntl();
   const [open, setOpen] = useState(false);
-  const [title, setTitle] = useState<React.ReactNode>();
 
   const actionRef = useRef<ActionType>();
   const [editableKeys, setEditableRowKeys] = useState<React.Key[]>([]);
@@ -374,7 +373,6 @@ const TaskList = ({
           search: true,
         }}
         actionRef={actionRef}
-        headerTitle={title}
         // 关闭默认的新建按钮
         recordCreatorProps={
           editable
@@ -428,7 +426,6 @@ const TaskList = ({
         }}
         value={dataSource}
         onChange={(value: readonly ITaskData[]) => {
-          const root = value.find((item) => item.id === projectId);
           setDataSource(value);
         }}
         editable={{

+ 4 - 27
dashboard-v4/dashboard/src/components/task/TaskProjects.tsx

@@ -1,15 +1,7 @@
 import { ActionType, ProTable } from "@ant-design/pro-components";
 import { FormattedMessage, useIntl } from "react-intl";
 import { Link } from "react-router-dom";
-import {
-  Alert,
-  Badge,
-  Button,
-  message,
-  Modal,
-  Popover,
-  Typography,
-} from "antd";
+import { Alert, Badge, Button, message, Modal, Popover } from "antd";
 import { Dropdown } from "antd";
 import {
   ExclamationCircleOutlined,
@@ -22,15 +14,12 @@ import { TChannelType } from "../api/Channel";
 import { PublicityValueEnum } from "../studio/table";
 import { IDeleteResponse } from "../api/Article";
 import { useEffect, useRef, useState } from "react";
-import StudioName, { IStudio } from "../auth/Studio";
 
 import { getSorterUrl } from "../../utils";
 import { TransferOutLinedIcon } from "../../assets/icon";
 import { IProjectData, IProjectListResponse } from "../api/task";
 import ProjectCreate from "./ProjectCreate";
 
-const { Text } = Typography;
-
 export const channelTypeFilter = {
   all: {
     text: <FormattedMessage id="channel.type.all.title" />,
@@ -93,11 +82,9 @@ const ProjectListWidget = ({
   onSelect,
 }: IWidget) => {
   const intl = useIntl();
-
   const [activeKey, setActiveKey] = useState<React.Key | undefined>("all");
-  const [myNumber, setMyNumber] = useState<number>(0);
-  const [collaborationNumber, setCollaborationNumber] = useState<number>(0);
   const [openCreate, setOpenCreate] = useState(false);
+
   useEffect(() => {
     ref.current?.reload();
   }, [disableChannels]);
@@ -319,21 +306,11 @@ const ProjectListWidget = ({
             items: [
               {
                 key: "all",
-                label: (
-                  <span>
-                    {intl.formatMessage({ id: "labels.all" })}
-                    {renderBadge(myNumber, activeKey === "all")}
-                  </span>
-                ),
+                label: intl.formatMessage({ id: "labels.all" }),
               },
               {
                 key: "workflow",
-                label: (
-                  <span>
-                    {intl.formatMessage({ id: "labels.workflow" })}
-                    {renderBadge(collaborationNumber, activeKey === "workflow")}
-                  </span>
-                ),
+                label: intl.formatMessage({ id: "labels.workflow" }),
               },
             ],
             onChange(key) {

+ 25 - 13
dashboard-v4/dashboard/src/components/task/TaskReader.tsx

@@ -10,8 +10,9 @@ import User from "../auth/User";
 import TimeShow from "../general/TimeShow";
 import TaskEditButton, { TRelation } from "./TaskEditButton";
 import PreTask from "./PreTask";
+import Like from "../like/Like";
 
-const { Text, Title } = Typography;
+const { Title } = Typography;
 
 export const Milestone = ({ task }: { task?: ITaskData }) => {
   return task?.is_milestone ? (
@@ -64,19 +65,29 @@ const TaskReader = ({ taskId, task, onLoad, onChange, onEdit }: IWidget) => {
       studio_name: "",
     };
     if (type === "pre") {
-      const hasPre = task?.pre_task?.find((value)=>value.id===data.id)
-      if(hasPre){
-        setting.pre_task_id = task?.pre_task?.filter((value)=>value.id!==data.id).map((item)=>item.id).join()
-      }else{
-        const newRelation = task?.pre_task? [...task.pre_task.map((item)=>item.id),data.id]:[data.id];
+      const hasPre = task?.pre_task?.find((value) => value.id === data.id);
+      if (hasPre) {
+        setting.pre_task_id = task?.pre_task
+          ?.filter((value) => value.id !== data.id)
+          .map((item) => item.id)
+          .join();
+      } else {
+        const newRelation = task?.pre_task
+          ? [...task.pre_task.map((item) => item.id), data.id]
+          : [data.id];
         setting.pre_task_id = newRelation.join();
       }
     } else if (type === "next") {
-      const hasPre = task?.next_task?.find((value)=>value.id===data.id)
-      if(hasPre){
-        setting.next_task_id = task?.next_task?.filter((value)=>value.id!==data.id).map((item)=>item.id).join()
-      }else{
-        const newRelation = task?.next_task? [...task.next_task.map((item)=>item.id),data.id]:[data.id];
+      const hasPre = task?.next_task?.find((value) => value.id === data.id);
+      if (hasPre) {
+        setting.next_task_id = task?.next_task
+          ?.filter((value) => value.id !== data.id)
+          .map((item) => item.id)
+          .join();
+      } else {
+        const newRelation = task?.next_task
+          ? [...task.next_task.map((item) => item.id), data.id]
+          : [data.id];
         setting.next_task_id = newRelation.join();
       }
     }
@@ -107,7 +118,7 @@ const TaskReader = ({ taskId, task, onLoad, onChange, onEdit }: IWidget) => {
               updatePreTask("pre", data);
               setOpenPreTask(false);
             }}
-            onTagClick={()=>setOpenPreTask(true)}
+            onTagClick={() => setOpenPreTask(true)}
             onClose={() => setOpenPreTask(false)}
           />
           <PreTask
@@ -119,7 +130,7 @@ const TaskReader = ({ taskId, task, onLoad, onChange, onEdit }: IWidget) => {
               setOpenNextTask(false);
             }}
             onClose={() => setOpenNextTask(false)}
-            onTagClick={()=>setOpenNextTask(true)}
+            onTagClick={() => setOpenNextTask(true)}
           />
         </Space>
         <div>
@@ -144,6 +155,7 @@ const TaskReader = ({ taskId, task, onLoad, onChange, onEdit }: IWidget) => {
         <Space>
           <User {...task?.editor} />
           <TimeShow updatedAt={task?.updated_at} />
+          <Like resId={task?.id} resType="task" />
         </Space>
       </div>
       <Divider />

+ 11 - 11
dashboard-v4/dashboard/src/components/task/TaskRelation.tsx

@@ -1,6 +1,4 @@
-import { useEffect, useState } from "react";
-import { get } from "../../request";
-import {  ITaskData, ITaskListResponse } from "../api/task";
+import { ITaskData } from "../api/task";
 
 import "../article/article.css";
 
@@ -8,10 +6,9 @@ import Mermaid from "../general/Mermaid";
 
 interface IWidget {
   projectId?: string;
-  tasks?:ITaskData[];
+  tasks?: ITaskData[];
 }
 const TaskRelation = ({ tasks }: IWidget) => {
-
   let mermaidText = "flowchart LR\n";
 
   //节点样式
@@ -30,7 +27,7 @@ const TaskRelation = ({ tasks }: IWidget) => {
     mermaidText += `classDef ${value.status} fill:${value.fill},stroke:#333,stroke-width:2px;\n`;
   });
 
-  let relationLine = new Map<string,number>();
+  let relationLine = new Map<string, number>();
   tasks?.forEach((task: ITaskData, index: number, array: ITaskData[]) => {
     //输出节点
     mermaidText += `${task.id}[${task.title}]:::${task.status};\n`;
@@ -48,14 +45,17 @@ const TaskRelation = ({ tasks }: IWidget) => {
     }
 
     //关系线
-    task.pre_task?.map((item)=>relationLine.set(`${item.id} --> ${task.id};\n`,0))
-    task.next_task?.map((item)=>relationLine.set(`${task.id} --> ${item.id};\n`,0))
-
+    task.pre_task?.map((item) =>
+      relationLine.set(`${item.id} --> ${task.id};\n`, 0)
+    );
+    task.next_task?.map((item) =>
+      relationLine.set(`${task.id} --> ${item.id};\n`, 0)
+    );
   });
 
-  Array.from(relationLine.keys()).forEach((value)=>{
+  Array.from(relationLine.keys()).forEach((value) => {
     mermaidText += value;
-  })
+  });
 
   console.debug(mermaidText);