Skip to content
项目
群组
代码片段
帮助
当前项目
正在载入...
登录 / 注册
切换导航面板
C
center-book
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
图表
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
日程
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
图像
聊天
创建新问题
作业
提交
问题看板
Open sidebar
EzijingWeb
center-book
Commits
77cf6ebc
提交
77cf6ebc
authored
7月 20, 2026
作者:
王鹏飞
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
refactor(admin): move legacy implementations into modules
上级
9ec935b8
隐藏空白字符变更
内嵌
并排
正在显示
85 个修改的文件
包含
5007 行增加
和
5425 行删除
+5007
-5425
Layout.jsx
apps/admin/src/modules/access/Layout.jsx
+5
-0
api.ts
apps/admin/src/modules/access/role/api.ts
+4
-0
routes.tsx
apps/admin/src/modules/access/routes.tsx
+1
-1
oss.js
apps/admin/src/modules/account/oss.js
+23
-0
AccountView.jsx
apps/admin/src/modules/account/views/AccountView.jsx
+171
-1
Layout.jsx
apps/admin/src/modules/advertisements/Layout.jsx
+5
-0
api.ts
apps/admin/src/modules/advertisements/advert/api.ts
+1
-2
AdvertImageUpload.tsx
...es/advertisements/advert/components/AdvertImageUpload.tsx
+1
-1
routes.tsx
apps/admin/src/modules/advertisements/routes.tsx
+1
-1
api.ts
apps/admin/src/modules/auth/api.ts
+1
-1
login-api.js
apps/admin/src/modules/auth/request/login-api.js
+5
-0
login.js
apps/admin/src/modules/auth/request/login.js
+38
-0
sso-api.js
apps/admin/src/modules/auth/request/sso-api.js
+9
-0
sso.js
apps/admin/src/modules/auth/request/sso.js
+121
-0
AuthLayout.jsx
apps/admin/src/modules/auth/views/AuthLayout.jsx
+20
-1
AuthLayout.less
apps/admin/src/modules/auth/views/AuthLayout.less
+0
-0
LoginView.jsx
apps/admin/src/modules/auth/views/LoginView.jsx
+288
-1
LoginView.less
apps/admin/src/modules/auth/views/LoginView.less
+0
-0
BooksLayout.jsx
apps/admin/src/modules/books/BooksLayout.jsx
+7
-0
audit.less
apps/admin/src/modules/books/audit/audit.less
+33
-0
PreviewModal.jsx
...admin/src/modules/books/audit/components/PreviewModal.jsx
+908
-1
routes.tsx
apps/admin/src/modules/books/audit/routes.tsx
+1
-1
AuditDetailView.tsx
apps/admin/src/modules/books/audit/views/AuditDetailView.tsx
+1
-1
api.ts
apps/admin/src/modules/books/management/api.ts
+14
-0
api.js
apps/admin/src/modules/books/management/chapters/api.js
+56
-0
BindKnowledgeGraph.jsx
...oks/management/chapters/components/BindKnowledgeGraph.jsx
+74
-0
EditChapterEditors.jsx
...oks/management/chapters/components/EditChapterEditors.jsx
+50
-0
form-chapter-title.jsx
...oks/management/chapters/components/form-chapter-title.jsx
+98
-0
index.jsx
apps/admin/src/modules/books/management/chapters/index.jsx
+616
-0
index.less
apps/admin/src/modules/books/management/chapters/index.less
+130
-0
request.js
apps/admin/src/modules/books/management/chapters/request.js
+360
-0
ManagementAddEdit.jsx
...modules/books/management/components/ManagementAddEdit.jsx
+111
-1
ManagementChapter.jsx
...modules/books/management/components/ManagementChapter.jsx
+1
-1
routes.tsx
apps/admin/src/modules/books/routes.tsx
+1
-1
api.ts
apps/admin/src/modules/books/sale/api.ts
+21
-0
SaleDiscussionDetail.jsx
...rc/modules/books/sale/components/SaleDiscussionDetail.jsx
+245
-1
SaleDiscussionDetail.less
...c/modules/books/sale/components/SaleDiscussionDetail.less
+37
-0
SaleEdit.jsx
apps/admin/src/modules/books/sale/components/SaleEdit.jsx
+570
-1
Layout.jsx
apps/admin/src/modules/members/Layout.jsx
+5
-0
routes.tsx
apps/admin/src/modules/members/routes.tsx
+1
-1
SettingsLayout.jsx
apps/admin/src/modules/settings/SettingsLayout.jsx
+11
-0
AppVersionListView.jsx
...modules/settings/app-version/views/AppVersionListView.jsx
+1
-1
request.js
apps/admin/src/modules/settings/help/request.js
+23
-0
HelpAddEditView.jsx
...admin/src/modules/settings/help/views/HelpAddEditView.jsx
+1
-1
routes.tsx
apps/admin/src/modules/settings/routes.tsx
+1
-1
api.ts
apps/admin/src/modules/student-book/api.ts
+1
-1
request.js
apps/admin/src/modules/student-book/request.js
+360
-0
StudentBookView.jsx
.../admin/src/modules/student-book/views/StudentBookView.jsx
+524
-1
StudentBookView.less
...admin/src/modules/student-book/views/StudentBookView.less
+0
-0
Layout.jsx
apps/admin/src/modules/teachers/Layout.jsx
+5
-0
TeacherListView.jsx
...admin/src/modules/teachers/list/views/TeacherListView.jsx
+2
-2
routes.tsx
apps/admin/src/modules/teachers/routes.tsx
+1
-1
index.jsx
apps/admin/src/pages/advertisement/index.jsx
+1
-6
index.less
apps/admin/src/pages/books/audit/index.less
+1
-33
preview.jsx
apps/admin/src/pages/books/audit/preview.jsx
+1
-908
index.jsx
apps/admin/src/pages/books/index.jsx
+1
-7
index.jsx
apps/admin/src/pages/books/management/addedit/index.jsx
+1
-111
index.jsx
apps/admin/src/pages/books/sale/discussDetail/index.jsx
+1
-245
index.less
apps/admin/src/pages/books/sale/discussDetail/index.less
+1
-37
edit.jsx
apps/admin/src/pages/books/sale/edit.jsx
+1
-570
api.js
apps/admin/src/pages/books/section/api.js
+1
-56
BindKnowledgeGraph.jsx
...src/pages/books/section/components/BindKnowledgeGraph.jsx
+1
-74
EditChapterEditors.jsx
...src/pages/books/section/components/EditChapterEditors.jsx
+1
-50
form-chapter-title.jsx
...src/pages/books/section/components/form-chapter-title.jsx
+1
-98
index.jsx
apps/admin/src/pages/books/section/index.jsx
+1
-616
index.less
apps/admin/src/pages/books/section/index.less
+1
-130
request.js
apps/admin/src/pages/books/section/request.js
+1
-360
index.jsx
apps/admin/src/pages/jurisdiction/index.jsx
+1
-11
index.jsx
apps/admin/src/pages/member/index.jsx
+1
-10
requet.js
apps/admin/src/pages/setting/help/addedit/requet.js
+1
-23
index.jsx
apps/admin/src/pages/setting/index.jsx
+1
-11
index.jsx
apps/admin/src/pages/setting/personalized-setting/index.jsx
+1
-88
index.jsx
apps/admin/src/pages/setting/point-management/index.jsx
+1
-236
request.js
apps/admin/src/pages/setting/point-management/request.js
+11
-152
index.jsx
apps/admin/src/pages/student/book/index.jsx
+1
-524
request.js
apps/admin/src/pages/student/book/request.js
+1
-360
index.jsx
apps/admin/src/pages/teacher/index.jsx
+1
-8
index.jsx
apps/admin/src/pages/user-module/index.jsx
+1
-20
index.jsx
apps/admin/src/pages/user-module/login/index.jsx
+1
-288
api.js
apps/admin/src/pages/user-module/login/request/api.js
+1
-5
index.js
apps/admin/src/pages/user-module/login/request/index.js
+1
-38
api.js
apps/admin/src/pages/user-module/request/api.js
+1
-9
request.jsx
apps/admin/src/pages/user-module/request/request.jsx
+1
-121
index.jsx
apps/admin/src/pages/user-module/userInfo/index.jsx
+1
-171
requet.js
apps/admin/src/pages/user-module/userInfo/requet.js
+1
-23
没有找到文件。
apps/admin/src/modules/access/Layout.jsx
0 → 100644
浏览文件 @
77cf6ebc
import
{
Outlet
}
from
'react-router'
const
AccessLayout
=
()
=>
<
Outlet
/>
export
default
AccessLayout
apps/admin/src/modules/access/role/api.ts
浏览文件 @
77cf6ebc
...
...
@@ -100,3 +100,7 @@ export function setRolePermissions(roleId: number, powerList: Array<string | num
export
function
getAllPermissions
()
{
return
getPayload
<
PermissionRecord
[]
>
(
"/system/power/getListAll"
,
{});
}
export
function
getTeacherRoles
()
{
return
getPayload
<
RoleRow
[]
>
(
"/user/role/getAllList"
,
{
type
:
2
});
}
apps/admin/src/modules/access/routes.tsx
浏览文件 @
77cf6ebc
...
...
@@ -2,7 +2,7 @@ import { lazy } from "react";
import
{
Navigate
,
useRoutes
}
from
"react-router"
;
import
ModuleBoundary
from
"@/app/ModuleBoundary"
;
const
AccessLayout
=
lazy
(()
=>
import
(
"
@/pages/jurisdiction/index
"
));
const
AccessLayout
=
lazy
(()
=>
import
(
"
./Layout
"
));
const
AdminRoutes
=
lazy
(()
=>
import
(
"./admin/routes"
));
const
RoleRoutes
=
lazy
(()
=>
import
(
"./role/routes"
));
...
...
apps/admin/src/modules/account/oss.js
0 → 100644
浏览文件 @
77cf6ebc
import
axios
from
"@/utils/request.js"
;
import
qs
from
"qs"
;
import
{
message
}
from
"antd"
;
import
{
get
}
from
"lodash-es"
;
// oss
const
getAliOSSSTSToken
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/common/OssUpload/getUploadToken"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"获取OSS令牌失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
export
{
getAliOSSSTSToken
};
apps/admin/src/modules/account/views/AccountView.jsx
浏览文件 @
77cf6ebc
export
{
default
}
from
"@/pages/user-module/userInfo"
;
import
{
useState
,
useEffect
}
from
"react"
;
import
{
Form
,
Input
,
Button
,
Space
,
Row
,
Col
,
Drawer
}
from
"antd"
;
import
users
from
"@/assets/images/icon/userinfo.png"
;
import
{
changePass
,
changeInfo
,
getUserInfo
}
from
"@/layout/request"
;
import
{
useNavigate
}
from
"react-router"
;
import
UploadImage
from
"@/components/uploadImage"
;
const
UserInfo
=
()
=>
{
const
navigator
=
useNavigate
();
const
[
imageUrl
,
setImageUrl
]
=
useState
(
""
);
const
[
form
]
=
Form
.
useForm
();
const
[
form2
]
=
Form
.
useForm
();
const
[
showModal
,
setShowModal
]
=
useState
(
false
);
const
fetchData
=
async
(
obj
)
=>
{
try
{
const
userData
=
await
getUserInfo
(
obj
);
form
.
setFieldsValue
({
real_name
:
userData
.
real_name
,
tel
:
userData
.
tel
,
pic
:
userData
.
pic
,
});
const
imageUrl
=
userData
.
pic
;
setImageUrl
(
imageUrl
);
}
catch
(
error
)
{
console
.
error
(
"Error fetching user data:"
,
error
);
}
};
useEffect
(()
=>
{
fetchData
();
},
[]);
const
changePasswrd
=
async
(
obj
)
=>
{
await
changePass
(
obj
);
setShowModal
(
false
);
};
const
submitForm
=
async
(
obj
)
=>
{
const
updatedData
=
await
changeInfo
({
...
obj
});
if
(
updatedData
)
{
await
fetchData
();
}
};
return
(
<
div
style=
{
{
padding
:
"20px 0"
}
}
>
<
Form
form=
{
form
}
labelCol=
{
{
span
:
2
}
}
onFinish=
{
submitForm
}
>
<
Form
.
Item
label=
"头像"
className=
"uploadPar"
name=
"pic"
>
<
UploadImage
value=
{
imageUrl
?
imageUrl
:
users
}
onChange=
{
(
url
)
=>
setImageUrl
(
url
)
}
></
UploadImage
>
</
Form
.
Item
>
<
Form
.
Item
label=
"管理员名称"
name=
"real_name"
rules=
{
[{
required
:
true
,
message
:
"请输入管理员名称"
}]
}
>
<
Input
autoComplete=
"off"
placeholder=
"请输入管理员名称"
style=
{
{
width
:
200
}
}
></
Input
>
</
Form
.
Item
>
<
Form
.
Item
label=
"手机号"
name=
"tel"
rules=
{
[{
required
:
true
,
message
:
"请输入手机号"
}]
}
>
<
Input
autoComplete=
"off"
placeholder=
"请输入手机号"
style=
{
{
width
:
200
}
}
></
Input
>
</
Form
.
Item
>
<
Form
.
Item
label=
"密码"
>
<
Button
type=
"primary"
ghost
onClick=
{
()
=>
setShowModal
(
true
)
}
>
修改密码
</
Button
>
</
Form
.
Item
>
<
Form
.
Item
wrapperCol=
{
{
offset
:
2
}
}
>
<
Space
size=
{
20
}
>
<
Button
onClick=
{
()
=>
navigator
(
-
1
)
}
>
取消
</
Button
>
<
Button
type=
"primary"
htmlType=
"submit"
>
保存
</
Button
>
</
Space
>
</
Form
.
Item
>
</
Form
>
<
Drawer
open=
{
showModal
}
onClose=
{
()
=>
{
setShowModal
(
false
);
}
}
mask=
{
false
}
maskClosable=
{
false
}
footer=
{
null
}
centered
destroyOnClose
keyboard=
{
false
}
>
<
Form
labelCol=
{
{
span
:
5
}
}
form=
{
form2
}
onFinish=
{
changePasswrd
}
>
<
Form
.
Item
label=
"旧密码"
name=
"old_password"
rules=
{
[{
required
:
true
,
message
:
"请输入旧密码"
}]
}
>
<
Input
.
Password
autoComplete=
"off"
placeholder=
"请输入旧密码"
></
Input
.
Password
>
</
Form
.
Item
>
<
Form
.
Item
label=
"新密码"
name=
"password"
rules=
{
[
{
required
:
true
,
message
:
"请输入新密码"
},
{
pattern
:
/^
((?=
.*
\d)(?=
.*
[
A-Z
])(?=
.*
[
a-z
]))
|
((?=
.*
\d)(?=
.*
[
A-Z
])(?=
.*
[\W
_
]))
|
((?=
.*
\d)(?=
.*
[
a-z
])(?=
.*
[\W
_
]))
|
((?=
.*
[
A-Z
])(?=
.*
[
a-z
])(?=
.*
[\W
_
]))[
a-zA-Z
\d\W
_
]
{8,12}$/
,
message
:
"密码至少满足三种格式(大写字母、小写字母、数字、特殊符号), 长度为8~12位字符!"
,
trigger
:
[
"change"
,
"blur"
],
},
{
validator
(
_
,
value
)
{
if
(
!
value
||
form2
.
getFieldValue
(
"re_password"
)
===
value
)
{
form2
.
setFields
([{
name
:
"re_password"
,
errors
:
[
""
]
}]);
return
Promise
.
resolve
();
}
form2
.
setFields
([{
name
:
"re_password"
,
errors
:
[
"两次输入的密码不一致"
]
}]);
return
Promise
.
reject
();
},
},
]
}
>
<
Input
.
Password
autoComplete=
"off"
placeholder=
"请输入新密码"
></
Input
.
Password
>
</
Form
.
Item
>
<
Form
.
Item
label=
"确认新密码"
name=
"re_password"
rules=
{
[
{
required
:
true
,
message
:
"请确认新密码"
},
{
pattern
:
/^
((?=
.*
\d)(?=
.*
[
A-Z
])(?=
.*
[
a-z
]))
|
((?=
.*
\d)(?=
.*
[
A-Z
])(?=
.*
[\W
_
]))
|
((?=
.*
\d)(?=
.*
[
a-z
])(?=
.*
[\W
_
]))
|
((?=
.*
[
A-Z
])(?=
.*
[
a-z
])(?=
.*
[\W
_
]))[
a-zA-Z
\d\W
_
]
{8,12}$/
,
message
:
"密码至少满足三种格式(大写字母、小写字母、数字、特殊符号), 长度为8~12位字符!"
,
trigger
:
[
"change"
,
"blur"
],
},
{
validator
(
_
,
value
)
{
if
(
!
value
||
form2
.
getFieldValue
(
"password"
)
===
value
)
{
return
Promise
.
resolve
();
}
return
Promise
.
reject
(
new
Error
(
"两次输入的密码不一致"
));
},
},
]
}
>
<
Input
.
Password
autoComplete=
"off"
placeholder=
"请确认新密码"
></
Input
.
Password
>
</
Form
.
Item
>
<
Form
.
Item
wrapperCol=
{
{
offset
:
5
}
}
>
<
Row
justify=
{
"center"
}
>
<
Space
size=
{
20
}
>
<
Col
>
<
Button
onClick=
{
()
=>
setShowModal
(
false
)
}
>
取消
</
Button
>
</
Col
>
<
Col
>
<
Button
type=
"primary"
htmlType=
"submit"
>
确认
</
Button
>
</
Col
>
</
Space
>
</
Row
>
</
Form
.
Item
>
</
Form
>
</
Drawer
>
</
div
>
);
};
export
default
UserInfo
;
apps/admin/src/modules/advertisements/Layout.jsx
0 → 100644
浏览文件 @
77cf6ebc
import
{
Outlet
}
from
'react-router'
const
AdvertisementsLayout
=
()
=>
<
Outlet
/>
export
default
AdvertisementsLayout
apps/admin/src/modules/advertisements/advert/api.ts
浏览文件 @
77cf6ebc
...
...
@@ -2,7 +2,6 @@ import { message } from "antd";
// @ts-expect-error The legacy workspace does not install the qs declaration package.
import
qs
from
"qs"
;
import
{
getPositionList
as
getLegacyPositionList
}
from
"@/pages/advertisement/adsense/request.js"
;
import
request
from
"@/utils/request.js"
;
import
type
{
AdvertQuery
}
from
"./query"
;
...
...
@@ -90,7 +89,7 @@ export function changeAdvertStatus(id: number, status: "0" | "1") {
}
export
async
function
getAdvertPositions
():
Promise
<
AdvertPosition
[]
|
false
>
{
const
result
=
await
get
LegacyPositionList
(
);
const
result
=
await
get
Payload
<
{
list
?:
AdvertPosition
[]
}
>
(
"/ad/position/getList"
,
{}
);
if
(
!
result
)
return
false
;
return
(
result
.
list
??
[])
as
AdvertPosition
[];
}
apps/admin/src/modules/advertisements/advert/components/AdvertImageUpload.tsx
浏览文件 @
77cf6ebc
...
...
@@ -5,7 +5,7 @@ import type { UploadProps } from "antd";
import
dayjs
from
"dayjs"
;
import
{
useEffect
,
useState
}
from
"react"
;
import
{
getAliOSSSTSToken
}
from
"@/
pages/setting/help/addedit/reque
t"
;
import
{
getAliOSSSTSToken
}
from
"@/
modules/settings/help/reques
t"
;
interface
OssClient
{
put
:
(
path
:
string
,
file
:
File
)
=>
Promise
<
{
url
:
string
}
>
;
...
...
apps/admin/src/modules/advertisements/routes.tsx
浏览文件 @
77cf6ebc
...
...
@@ -2,7 +2,7 @@ import { lazy } from "react";
import
{
Navigate
,
useRoutes
}
from
"react-router"
;
import
ModuleBoundary
from
"@/app/ModuleBoundary"
;
const
AdvertisementsLayout
=
lazy
(()
=>
import
(
"
@/pages/advertisement/index
"
));
const
AdvertisementsLayout
=
lazy
(()
=>
import
(
"
./Layout
"
));
const
AdvertRoutes
=
lazy
(()
=>
import
(
"./advert/routes"
));
const
AdsenseRoutes
=
lazy
(()
=>
import
(
"./adsense/routes"
));
...
...
apps/admin/src/modules/auth/api.ts
浏览文件 @
77cf6ebc
export
*
from
"
@/pages/user-module/login/request
"
;
export
*
from
"
./request/login
"
;
apps/admin/src/modules/auth/request/login-api.js
0 → 100644
浏览文件 @
77cf6ebc
const
api
=
{
login
:
"/user/login"
,
getCode
:
"/user/login/verify"
,
};
export
default
api
;
apps/admin/src/modules/auth/request/login.js
0 → 100644
浏览文件 @
77cf6ebc
import
axios
from
"@/utils/request.js"
;
import
qs
from
"qs"
;
import
api
from
"./login-api.js"
;
import
{
get
}
from
"lodash-es"
;
import
{
message
}
from
"antd"
;
const
login
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
api
.
login
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
msg
===
"Undefined index: verify"
)
{
message
.
error
(
"请先点击按钮进行验证"
);
return
false
;
}
if
(
code
===
200
)
{
return
{
code
,
...
data
,
};
}
else
{
message
.
error
(
msg
);
return
{
msg
,
};
}
}
catch
(
error
)
{
console
.
error
(
error
);
let
msg
=
get
(
error
.
response
,
"data.msg"
,
""
);
if
(
msg
)
{
message
.
error
(
msg
);
}
return
false
;
}
};
export
{
login
};
apps/admin/src/modules/auth/request/sso-api.js
0 → 100644
浏览文件 @
77cf6ebc
const
api
=
{
getCaptcha
:
"/users/generate_image_code"
,
// 获取验证码 GET
sendSMS
:
"/docapi/users/send_sms"
,
// 发送手机验证码
loginByPhone
:
"/docapi/users/phone_login"
,
// 手机号码形式登录
loginByUsername
:
"/docapi/users/login"
,
// 用户名密码登录
checkSSOLogin
:
"/user/checkSsoLogin"
,
// sso 登录
};
export
default
api
;
apps/admin/src/modules/auth/request/sso.js
0 → 100644
浏览文件 @
77cf6ebc
import
axios
from
"@/utils/request"
;
import
{
message
}
from
"antd"
;
import
api
from
"./sso-api"
;
import
{
get
}
from
"lodash-es"
;
import
qs
from
"qs"
;
// 获取图形验证码
const
getCaptcha
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
get
(
api
.
getCaptcha
,
obj
);
const
status
=
get
(
res
,
"status"
,
0
);
const
data
=
get
(
res
,
"data"
,
""
);
if
(
status
===
200
)
{
return
data
;
}
}
catch
(
error
)
{
console
.
error
(
error
);
let
msg
=
get
(
error
.
response
,
"data.msg"
,
""
);
if
(
msg
)
{
message
.
error
(
msg
);
}
return
false
;
}
};
// 发送短信验证码
const
sendSMS
=
async
(
data
)
=>
{
try
{
const
res
=
await
axios
.
post
(
api
.
sendSMS
,
data
);
const
code
=
get
(
res
,
"data.code"
,
0
);
const
msg
=
get
(
res
,
"data.message"
,
"发送失败"
);
if
(
code
===
200
)
{
message
.
success
(
"验证码发送成功!"
);
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
console
.
error
(
error
);
let
msg
=
get
(
error
.
response
,
"data.msg"
,
""
);
if
(
msg
)
{
message
.
error
(
msg
);
}
return
false
;
}
};
// 用户名登录
const
userNameLogin
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
api
.
loginByUsername
,
obj
);
const
code
=
get
(
res
,
"data.code"
,
0
);
const
msg
=
get
(
res
,
"data.message"
,
"登录失败"
);
const
data
=
get
(
res
,
"data.data"
,
""
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
console
.
error
(
error
);
let
msg
=
get
(
error
.
response
,
"data.msg"
,
""
);
if
(
msg
)
{
message
.
error
(
msg
);
}
return
false
;
}
};
// 手机号登录
const
phoneLogin
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
api
.
loginByPhone
,
obj
);
const
code
=
get
(
res
,
"data.code"
,
0
);
const
msg
=
get
(
res
,
"data.message"
,
"登录失败"
);
const
data
=
get
(
res
,
"data.data"
,
""
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
console
.
error
(
error
);
let
msg
=
get
(
error
.
response
,
"data.msg"
,
""
);
if
(
msg
)
{
message
.
error
(
msg
);
}
return
false
;
}
};
const
checkSSOLogin
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
api
.
checkSSOLogin
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
msg
===
"Undefined index: verify"
)
{
message
.
error
(
"请先点击按钮进行验证"
);
return
false
;
}
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
console
.
error
(
error
);
let
msg
=
get
(
error
.
response
,
"data.msg"
,
""
);
if
(
msg
)
{
message
.
error
(
msg
);
}
return
false
;
}
};
export
{
getCaptcha
,
sendSMS
,
userNameLogin
,
phoneLogin
,
checkSSOLogin
};
apps/admin/src/modules/auth/views/AuthLayout.jsx
浏览文件 @
77cf6ebc
export
{
default
}
from
"@/pages/user-module"
;
import
{
Outlet
}
from
"react-router"
;
import
"./AuthLayout.less"
;
const
User
=
()
=>
{
return
(
<
div
className=
"user-content"
>
<
div
className=
"form-container"
>
<
div
className=
"left-container"
></
div
>
<
div
className=
"form-field-box"
>
<
div
className=
"form-field"
>
<
Outlet
/>
</
div
>
</
div
>
</
div
>
</
div
>
);
};
export
default
User
;
apps/admin/src/
pages/user-module/index
.less
→
apps/admin/src/
modules/auth/views/AuthLayout
.less
浏览文件 @
77cf6ebc
File moved
apps/admin/src/modules/auth/views/LoginView.jsx
浏览文件 @
77cf6ebc
export
{
default
}
from
"@/pages/user-module/login"
;
import
React
,
{
useState
,
useEffect
,
useRef
}
from
"react"
;
import
{
Form
,
Input
,
Button
,
Row
,
Col
,
Space
,
Image
,
message
}
from
"antd"
;
import
{
CloseOutlined
,
UserOutlined
,
LockOutlined
}
from
"@ant-design/icons"
;
import
{
useNavigate
}
from
"react-router"
;
import
{
checkSSOLogin
}
from
"../request/sso"
;
import
"./LoginView.less"
;
import
loginBtn
from
"@/assets/images/loginBtn.png"
;
import
loginBtn2
from
"@/assets/images/loginBtn2.png"
;
import
Icon1
from
"@/assets/login/1.png"
;
import
Icon2
from
"@/assets/login/2.png"
;
import
Icon3
from
"@/assets/login/3.png"
;
import
Icon4
from
"@/assets/login/4.png"
;
import
Icon5
from
"@/assets/login/5.png"
;
import
Icon6
from
"@/assets/login/6.png"
;
import
Icon7
from
"@/assets/login/7.png"
;
import
Icon8
from
"@/assets/login/8.png"
;
import
Icon9
from
"@/assets/login/9.png"
;
import
Icon10
from
"@/assets/login/10.png"
;
import
Icon11
from
"@/assets/login/11.png"
;
import
Icon12
from
"@/assets/login/12.png"
;
import
Icon13
from
"@/assets/login/13.png"
;
import
Icon14
from
"@/assets/login/14.png"
;
import
Icon15
from
"@/assets/login/15.png"
;
import
Icon16
from
"@/assets/login/16.png"
;
import
Icon17
from
"@/assets/login/17.png"
;
import
Icon18
from
"@/assets/login/18.png"
;
import
Icon19
from
"@/assets/login/19.png"
;
import
Icon20
from
"@/assets/login/20.png"
;
import
Icon21
from
"@/assets/login/21.png"
;
import
Icon22
from
"@/assets/login/22.png"
;
import
Icon23
from
"@/assets/login/23.png"
;
import
{
login
}
from
"../request/login"
;
import
logo
from
"@/assets/images/icon_logo_login.png"
;
import
{
RotateVerify
}
from
"../../../utils/captcha"
;
const
Login
=
()
=>
{
const
navigate
=
useNavigate
();
const
[
form
]
=
Form
.
useForm
();
const
[
isActive
,
setIsActive
]
=
useState
(
0
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
captchaURL
,
setCaptchaURL
]
=
useState
(
""
);
const
[
captchaStatus
,
setCaptchaStatus
]
=
useState
(
false
);
const
[
captchaText
,
setCaptchaText
]
=
useState
(
"发送验证码"
);
const
[
showCaptcha
,
setShowCaptcha
]
=
useState
(
false
);
const
[
isLoggedIn
,
setIsLoggedIn
]
=
useState
(
false
);
//登录图片切换
const
[
isLoginBtn
,
setIsLoginBtn
]
=
useState
(
false
);
//是否点击了登录按钮
const
[
code
,
setCode
]
=
useState
(
""
);
const
[
disLf
,
setDisLf
]
=
useState
(
""
);
const
[
initialValues
,
setinitialValues
]
=
useState
({
tel
:
""
,
password
:
""
,
});
const
timer
=
useRef
();
const
getSSOInfo
=
async
()
=>
{
const
isSSOData
=
await
checkSSOLogin
();
if
(
isSSOData
.
run
===
"login"
)
{
}
else
if
(
isSSOData
.
token
)
{
localStorage
.
setItem
(
"kiwi.token"
,
isSSOData
.
token
);
navigate
(
"/"
);
return
;
}
};
useEffect
(()
=>
{
getSSOInfo
();
},
[]);
// 登录
const
toLayout
=
async
(
obj
)
=>
{
setLoading
(
true
);
if
(
isLoginBtn
)
{
const
data
=
await
login
({
...
obj
,
code
:
code
,
disLf
:
disLf
,
});
if
(
data
.
code
===
200
)
{
localStorage
.
setItem
(
"kiwi.token"
,
data
.
token
);
navigate
(
"/"
);
return
;
}
else
{
form
.
setFields
([{
name
:
"password"
,
errors
:
[
data
.
msg
]
}]);
}
}
else
{
message
.
error
(
"请先完成按钮验证"
);
}
// 刷新验证码
// await refreshCaptcha();
setLoading
(
false
);
};
const
handleStartVerification
=
()
=>
{
setShowCaptcha
(
true
);
};
// 点击×
const
handleGoBack
=
()
=>
{
setShowCaptcha
(
false
);
};
useEffect
(()
=>
{
if
(
showCaptcha
)
{
const
rotateWrapEle
=
document
.
querySelector
(
"#rotateWrap1"
);
// 确保 DOM 元素存在之后再进行初始化
if
(
rotateWrapEle
)
{
const
myRotateVerify
=
new
RotateVerify
(
"#rotateWrap1"
,
{
initText
:
"滑动将图片转正"
,
slideImage
:
[
Icon1
,
Icon2
,
Icon3
,
Icon4
,
Icon5
,
Icon6
,
Icon7
,
Icon8
,
Icon9
,
Icon10
,
Icon11
,
Icon12
,
Icon13
,
Icon14
,
Icon15
,
Icon16
,
Icon17
,
Icon18
,
Icon4
,
Icon19
,
Icon20
,
Icon21
,
Icon22
,
Icon23
,
],
initUrl
:
"/user/login/initData"
,
verifyUrl
:
"/user/login/checkVerify"
,
getSuccessState
:
function
(
data
)
{
console
.
log
(
"验证成功"
,
data
);
setDisLf
(
data
.
disLf
);
setCode
(
data
.
code
);
if
(
data
.
disLf
)
{
setShowCaptcha
(
false
);
setIsLoggedIn
(
true
);
setIsLoginBtn
(
true
);
}
},
});
// 如果需要,在这里处理卸载时清理资源
return
()
=>
{
console
.
log
(
myRotateVerify
.
initUrl
);
// 清理或销毁 RotateVerify 实例等操作
if
(
myRotateVerify
&&
typeof
myRotateVerify
.
destroy
===
"function"
)
{
myRotateVerify
.
destroy
();
}
};
}
}
},
[
showCaptcha
]);
return
(
<
div
className=
{
showCaptcha
?
"captcha-container"
:
"normal-container"
}
>
{
showCaptcha
&&
(
<
div
className=
"rotateWrap"
>
<
div
className=
"cuo"
onClick=
{
handleGoBack
}
>
<
CloseOutlined
/>
</
div
>
<
span
className=
"top-s"
>
身份验证
</
span
>
<
span
className=
"top-x"
>
拖动滑块,使图片角度为正
</
span
>
<
div
id=
"rotateWrap1"
>
<
div
className=
"rotateverify-contaniner"
>
<
div
className=
"rotate-can-wrap"
>
<
canvas
className=
"rotateCan rotate-can"
width=
"200"
height=
"200"
></
canvas
>
<
div
className=
"statusBg status-bg"
></
div
>
</
div
>
<
div
className=
"control-wrap slideDragWrap"
>
<
div
className=
"control-tips"
>
<
p
className=
"c-tips-txt cTipsTxt"
>
滑动将图片转正
</
p
>
</
div
>
<
div
className=
"control-bor-wrap controlBorWrap"
></
div
>
<
div
className=
"control-btn slideDragBtn"
>
{
/* <i className='control-btn-ico'></i> */
}
<
div
className=
"control-btn-ico"
></
div
>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
)
}
{
!
showCaptcha
&&
(
<
div
className=
"img-logo"
>
<
img
src=
{
logo
}
className=
"img"
/>
</
div
>
)
}
{
!
showCaptcha
&&
<
div
className=
"login-header"
>
欢迎登录
</
div
>
}{
" "
}
{
!
showCaptcha
&&
(
<
div
className=
"form-data-container"
>
<
Form
form=
{
form
}
onFinish=
{
toLayout
}
autoComplete=
"off"
initialValues=
{
initialValues
}
>
<>
<
Form
.
Item
name=
"tel"
rules=
{
[
{
required
:
true
,
message
:
"请输入手机号!"
},
{
pattern
:
/^1
[
3-9
]\d
{9}$/
,
message
:
"请输入正确格式的手机号"
},
]
}
>
<
Input
autoComplete=
"off"
prefix=
{
<
UserOutlined
/>
}
size=
"large"
placeholder=
"请输入手机号"
/>
</
Form
.
Item
>
<
Form
.
Item
name=
"password"
rules=
{
[{
required
:
true
,
message
:
"请输入密码!"
}]
}
>
<
Input
.
Password
autoComplete=
"off"
prefix=
{
<
LockOutlined
/>
}
size=
"large"
placeholder=
"请输入密码"
/>
</
Form
.
Item
>
{
/* <Form.Item name='verify' rules={[{ required: true, message: '请输入验证码!' }]}>
<Row gutter={16}>
<Col span={16}>
<Input
prefix={<SetIcon src={IconCaptcha} />}
size='large'
placeholder='请输入验证码'
/>
</Col>
<Col span={8}>
<img
src={captchaURL}
alt='图形验证码'
className='captcha'
onClick={refreshCaptcha}
/>
</Col>
</Row>
</Form.Item> */
}
<
Form
.
Item
name=
"verify"
onClick=
{
handleStartVerification
}
>
<
div
style=
{
{
width
:
"100%"
,
height
:
"100%"
,
pointerEvents
:
"none"
}
}
>
{
isLoggedIn
?
(
<
Image
src=
{
loginBtn2
}
style=
{
{
width
:
"100%"
,
height
:
"100%"
,
}
}
/>
)
:
(
<
Image
src=
{
loginBtn
}
style=
{
{
width
:
"100%"
,
height
:
"100%"
,
}
}
/>
)
}
</
div
>
</
Form
.
Item
>
</>
<
Form
.
Item
className=
"login-submit-btn"
>
<
Button
htmlType=
"submit"
loading=
{
loading
}
type=
"primary"
size=
"large"
>
登录
</
Button
>
</
Form
.
Item
>
</
Form
>
</
div
>
)
}
</
div
>
);
};
export
default
Login
;
apps/admin/src/
pages/user-module/login/index
.less
→
apps/admin/src/
modules/auth/views/LoginView
.less
浏览文件 @
77cf6ebc
File moved
apps/admin/src/modules/books/BooksLayout.jsx
0 → 100644
浏览文件 @
77cf6ebc
import
React
from
"react"
;
import
{
Outlet
}
from
"react-router"
;
const
Book
=
()
=>
{
return
<
Outlet
/>;
};
export
default
Book
;
apps/admin/src/modules/books/audit/audit.less
0 → 100644
浏览文件 @
77cf6ebc
.auditPanel {
background: #fbfbfb;
margin-bottom: 30px;
}
.auditdetail {
.ant-steps-item-icon {
width: 50px !important;
height: 100px;
}
.imgel {
.user {
width: 50px;
height: 50px;
position: relative;
background-size: cover;
}
.status {
width: 20px;
height: 20px;
position: absolute;
right: 0;
bottom: 0;
}
}
// .ant-steps-item-description {
// height: 150px;
// }
.ant-steps-item-tail {
display: none !important;
}
}
apps/admin/src/modules/books/audit/components/PreviewModal.jsx
浏览文件 @
77cf6ebc
export
{
default
}
from
"@/pages/books/audit/preview"
;
import
React
,
{
useEffect
,
useState
,
useRef
,
useImperativeHandle
,
forwardRef
}
from
"react"
;
import
{
Modal
,
Drawer
,
Tree
,
Spin
,
Space
,
Button
,
Input
,
Image
}
from
"antd"
;
import
{
CloseOutlined
,
LeftOutlined
,
BarsOutlined
}
from
"@ant-design/icons"
;
import
{
getInfoByChapterId
,
getAllList
}
from
"@/modules/books/management/chapters/request"
;
import
{
getChapterTopic
,
expandReadInfo
}
from
"@/common/wangeditor-customer/utils/request"
;
import
"@/common/wangeditor-customer/utils/iconfont"
;
import
{
convertToAntdTreeData
,
findTreeElementByKey
,
findFirstNotHasChildren
,
clacTopicText
,
}
from
"@/utils/common"
;
import
{
get
}
from
"lodash-es"
;
import
$
from
"jquery"
;
import
"@/common/preview.less"
;
import
hljs
from
"highlight.js"
;
import
"highlight.js/styles/github.css"
;
const
correctList
=
[
"A"
,
"B"
,
"C"
,
"D"
,
"E"
,
"F"
,
"G"
,
"H"
,
"I"
,
"J"
,
"K"
,
"L"
,
"M"
,
"N"
,
"O"
,
"P"
,
"Q"
,
"R"
,
"S"
,
"T"
,
"U"
,
"V"
,
"W"
,
"X"
,
"Y"
,
"Z"
,
];
// 递归遍历树,为叶子节点分配页码,返回 Map<key, pageNumber> 和总页数
const
buildPageMap
=
(
treeData
)
=>
{
const
map
=
{};
let
page
=
0
;
const
walk
=
(
nodes
)
=>
{
nodes
.
forEach
((
node
)
=>
{
if
(
node
.
children
&&
node
.
children
.
length
>
0
)
{
walk
(
node
.
children
);
}
else
{
page
+=
1
;
map
[
node
.
key
]
=
page
;
}
});
};
walk
(
treeData
);
return
{
map
,
total
:
page
};
};
const
PreviewScreen
=
(
props
,
ref
)
=>
{
const
{
bookId
}
=
props
;
const
[
gData
,
setGData
]
=
useState
([]);
const
[
loading
,
setLoading
]
=
useState
(
false
);
// 展示
const
[
prviewHtml
,
setPrviewHtml
]
=
useState
(
""
);
// 预览html
const
[
open
,
setOpen
]
=
useState
(
false
);
// 抽屉
const
[
secondType
,
setSecondType
]
=
useState
(
false
);
const
[
pageMap
,
setPageMap
]
=
useState
({});
// 叶子节点页码映射
const
[
totalPages
,
setTotalPages
]
=
useState
(
0
);
// 总页数
const
[
currentPage
,
setCurrentPage
]
=
useState
(
0
);
// 当前页码
const
[
toolTip
,
setTooltip
]
=
useState
({});
// 树节点设置
const
[
expandedKeys
,
setExpandedKeys
]
=
useState
([]);
const
[
checkedKeys
,
setCheckedKeys
]
=
useState
([]);
const
[
headTitle
,
setHeadTitle
]
=
useState
(
""
);
const
[
oldTitle
,
setOldTitle
]
=
useState
(
""
);
const
[
newChapterId
,
setNewChapterId
]
=
useState
(
""
);
const
[
opend
,
setOpend
]
=
useState
(
false
);
const
[
gallery
,
setGallery
]
=
useState
([]);
const
[
galleryIndex
,
setGalleryIndex
]
=
useState
(
0
);
const
[
isType
,
setIsType
]
=
useState
(
""
);
const
[
topicList
,
setTopicList
]
=
useState
([]);
// 题库
const
[
radioList
,
setRadioList
]
=
useState
([]);
// 单选
const
[
checkBoxList
,
setCheckBoxList
]
=
useState
([]);
// 多选
const
[
judgeList
,
setJudgeList
]
=
useState
([]);
// 判断
const
[
fillList
,
setFillList
]
=
useState
([]);
// 填空
const
[
textareaList
,
setTextareaList
]
=
useState
([]);
// 简答
const
[
expandContent
,
setExpandContent
]
=
useState
(
""
);
// 扩展阅读内容
const
[
previewImg
,
setPreviewImg
]
=
useState
(
""
);
// 扩展阅读内容
const
newId
=
useRef
(
null
);
// 递归函数,获取所有节点的key
const
getAllNodeKeys
=
(
nodes
)
=>
{
let
keys
=
[];
nodes
.
forEach
((
node
)
=>
{
keys
.
push
(
node
.
key
);
if
(
node
.
children
&&
node
.
children
.
length
>
0
)
{
keys
=
keys
.
concat
(
getAllNodeKeys
(
node
.
children
));
}
});
return
keys
;
};
const
getChapterTreeList
=
async
()
=>
{
setLoading
(
true
);
const
data
=
await
getAllList
({
book_id
:
bookId
});
if
(
!
data
.
data
.
length
)
{
setLoading
(
false
);
return
;
}
const
arr
=
convertToAntdTreeData
(
data
.
data
,
"name"
);
const
{
map
,
total
}
=
buildPageMap
(
arr
);
setPageMap
(
map
);
setTotalPages
(
total
);
setGData
(
arr
);
const
allKeys
=
getAllNodeKeys
(
arr
);
setExpandedKeys
(
allKeys
);
const
first
=
findFirstNotHasChildren
(
arr
);
setOldTitle
(
first
.
title
);
setNewChapterId
(
first
.
key
);
setCurrentPage
(
map
[
first
.
key
]
||
0
);
setLoading
(
false
);
};
const
getTopicList
=
async
({
practicenum
,
chapterid
,
bookid
})
=>
{
const
data
=
await
getChapterTopic
({
position
:
practicenum
,
book_id
:
bookid
,
chapter_id
:
chapterid
,
});
const
temp
=
[];
data
.
forEach
((
item
)
=>
{
let
obj
=
{
...
item
};
if
([
1
,
2
,
3
].
includes
(
parseInt
(
item
.
question_style
)))
{
try
{
obj
.
question_list
=
JSON
.
parse
(
item
.
question_list
);
}
catch
(
err
)
{
obj
.
question_list
=
[];
}
}
temp
.
push
(
obj
);
});
const
tempRadio
=
temp
.
filter
((
item
)
=>
parseInt
(
item
.
question_style
)
===
1
);
const
tempCheckbox
=
temp
.
filter
((
item
)
=>
parseInt
(
item
.
question_style
)
===
2
);
const
tempJudge
=
temp
.
filter
((
item
)
=>
parseInt
(
item
.
question_style
)
===
3
);
const
tempFill
=
temp
.
filter
((
item
)
=>
parseInt
(
item
.
question_style
)
===
4
);
const
tempTextarea
=
temp
.
filter
((
item
)
=>
parseInt
(
item
.
question_style
)
===
5
);
setRadioList
(
tempRadio
);
setCheckBoxList
(
tempCheckbox
);
setJudgeList
(
tempJudge
);
setFillList
(
tempFill
);
setTextareaList
(
tempTextarea
);
setTopicList
(
temp
);
};
const
getExpandInfo
=
async
({
chapterId
,
position
})
=>
{
const
data
=
await
expandReadInfo
({
book_id
:
bookId
,
chapter_id
:
chapterId
,
position
:
position
,
});
if
(
data
)
{
setExpandContent
(
data
.
content
);
}
};
const
getChapterId
=
async
()
=>
{
setLoading
(
true
);
newId
.
current
=
newChapterId
;
const
data
=
await
getInfoByChapterId
({
chapter_id
:
newChapterId
});
if
(
data
)
{
const
content
=
get
(
data
,
"content"
,
""
);
setPrviewHtml
(
content
);
$
(
"#previee-container"
)
.
find
(
"video audio"
)
.
each
((
index
,
item
)
=>
{
$
(
item
).
attr
(
"controls"
,
true
).
attr
(
"controlslist"
,
"nodownload"
);
});
setTimeout
(()
=>
{
document
.
querySelectorAll
(
".preview-content-show "
)[
0
]
.
querySelectorAll
(
"pre code"
)
.
forEach
((
el
)
=>
{
hljs
.
highlightElement
(
el
);
});
},
200
);
}
setLoading
(
false
);
};
const
handlerClick
=
()
=>
{
const
iParentW
=
$
(
".preview-content-it"
).
innerWidth
();
const
iParentH
=
$
(
".preview-content-it"
).
innerHeight
();
const
parentOffset
=
$
(
".preview-content-it"
).
offset
();
// 获取父元素相对于文档的位置
const
parentRect
=
$
(
".preview-content-it"
)[
0
].
getBoundingClientRect
();
// 获取父元素相对于文档的位置
$
(
".preview-content-html"
).
on
(
"click"
,
async
(
ev
)
=>
{
ev
.
stopPropagation
();
const
target
=
ev
.
target
;
const
tooltip
=
$
(
target
).
closest
(
".chapter-item-tooltip"
);
const
alink
=
$
(
target
).
closest
(
".chapter-item-link"
);
const
aGallery
=
$
(
target
).
closest
(
".chapter-gallery-container"
);
const
practice
=
$
(
target
).
closest
(
".chapter-practice"
);
const
aA
=
$
(
target
).
closest
(
"a"
);
const
aExpand
=
$
(
target
).
closest
(
".chapter-expand"
);
if
(
tooltip
.
length
>
0
)
{
const
random
=
$
(
tooltip
).
data
(
"random"
);
const
link
=
$
(
tooltip
).
data
(
"link"
);
const
content
=
$
(
tooltip
).
data
(
"content"
);
const
tooltipType
=
$
(
tooltip
).
data
(
"tooltip-type"
);
const
title
=
$
(
tooltip
).
data
(
"title"
);
const
tooltipParent
=
$
(
target
).
closest
(
"p, div, h1, h2, h3, h4, h5, h6, li, dt, dd, section"
,
);
const
tooltipParentPosition
=
$
(
tooltipParent
).
position
();
const
tooltipPosition
=
$
(
tooltip
).
position
();
const
childOffset
=
$
(
tooltip
).
offset
();
// 获取子元素相对于文档的位置
const
childWidth
=
$
(
tooltip
).
outerWidth
(
true
);
// 获取元素的宽度
const
childHeight
=
$
(
tooltip
).
outerHeight
(
true
);
// 获取元素的高度
let
toolRect
=
null
;
let
targetPositon
=
null
;
if
(
target
.
nodeName
.
toLowerCase
()
===
"svg"
||
target
.
nodeName
.
toLowerCase
()
===
"use"
)
{
toolRect
=
target
.
parentNode
.
parentNode
.
getBoundingClientRect
();
targetPositon
=
$
(
target
).
closest
(
".chapter-item-tooltip"
).
position
();
}
else
{
toolRect
=
target
.
parentNode
.
getBoundingClientRect
();
targetPositon
=
$
(
target
).
position
();
}
const
lineHeight
=
$
(
tooltip
).
css
(
"line-height"
);
let
inSide
=
""
;
let
left
=
childOffset
.
left
-
parentOffset
.
left
;
let
top
=
0
;
let
squareStyle
=
{};
if
(
childWidth
+
left
>=
iParentW
)
{
left
=
iParentW
-
childWidth
-
10
;
}
if
(
left
<=
0
)
{
left
=
10
;
}
let
style1
=
{
top
:
`
${
top
}
px`
,
left
:
`
${
left
}
px`
,
};
setTooltip
({
title
,
random
,
link
,
content
,
tooltipType
,
inSide
,
style
:
style1
,
squareStyle
:
squareStyle
,
});
setTimeout
(()
=>
{
const
childMoveHeight
=
$
(
".tooltip"
).
outerHeight
();
// 获取元素的高度
const
childMoveWidth
=
$
(
".tooltip"
).
outerWidth
();
// 获取元素的高度
const
scrollTop
=
$
(
".preview-content-show"
).
scrollTop
();
let
squareStyle
=
{};
if
(
left
<=
0
)
{
left
=
10
;
squareStyle
.
left
=
10
;
}
else
if
(
left
+
childMoveWidth
>=
iParentW
)
{
left
=
iParentW
-
childMoveWidth
-
20
;
squareStyle
.
left
=
targetPositon
.
left
-
left
;
}
else
{
squareStyle
.
left
=
childWidth
/
2
;
}
// 换行了
if
(
toolRect
.
height
/
1.5
>
parseInt
(
lineHeight
))
{
left
=
toolRect
.
right
-
parentRect
.
left
-
childMoveWidth
+
15
;
squareStyle
.
left
=
childMoveWidth
-
30
;
}
// 是否被遮挡
if
(
tooltipPosition
.
top
+
childMoveHeight
+
childHeight
+
30
>=
scrollTop
+
iParentH
)
{
inSide
=
"bottom"
;
top
=
tooltipPosition
.
top
-
childMoveHeight
;
squareStyle
.
left
=
childWidth
/
2
;
}
else
{
inSide
=
"top"
;
if
(
toolRect
.
height
/
1.5
>
parseInt
(
lineHeight
))
{
top
=
tooltipPosition
.
top
+
childHeight
;
}
else
{
top
=
tooltipPosition
.
top
+
childHeight
+
25
;
}
// squareStyle.left = childWidth / 2;
}
top
=
top
-
scrollTop
;
let
style2
=
{
top
:
`
${
top
}
px`
,
left
:
`
${
left
}
px`
,
zIndex
:
1000
,
opacity
:
1
,
};
setTooltip
({
title
,
random
,
link
,
content
,
tooltipType
,
inSide
,
style
:
style2
,
squareStyle
:
squareStyle
,
});
},
100
);
return
false
;
}
else
if
(
alink
.
length
>
0
)
{
const
link
=
$
(
alink
).
data
(
"link"
);
const
linkType
=
$
(
alink
).
data
(
"linktype"
);
const
chapters
=
$
(
alink
).
data
(
"chapters"
);
const
title
=
$
(
alink
).
data
(
"title"
);
let
last
=
""
;
if
(
parseInt
(
linkType
)
===
2
)
{
if
(
chapters
.
toString
().
indexOf
(
","
)
>
-
1
)
{
const
arr
=
chapters
.
split
(
","
);
last
=
arr
[
arr
.
length
-
1
];
}
else
{
last
=
chapters
;
}
setHeadTitle
(
title
);
setOpend
(
true
);
setNewChapterId
(
last
);
}
else
{
window
.
open
(
link
);
}
ev
.
preventDefault
();
return
false
;
}
else
if
(
aGallery
.
length
>
0
)
{
const
title
=
$
(
aGallery
).
data
(
"title"
);
const
flex
=
$
(
aGallery
).
data
(
"flex"
);
const
gallerylist
=
$
(
aGallery
).
data
(
"gallerylist"
);
const
galleryArr
=
JSON
.
parse
(
decodeURI
(
gallerylist
));
if
(
galleryArr
&&
galleryArr
.
length
>
0
&&
galleryArr
instanceof
Array
)
{
setGallery
(
galleryArr
);
if
(
parseInt
(
flex
)
===
2
)
{
setGalleryIndex
(
0
);
}
else
{
setGalleryIndex
(
$
(
target
).
closest
(
".chapter-gallery-item"
).
index
());
}
}
else
{
setGallery
([]);
}
setOpend
(
true
);
setHeadTitle
(
title
);
setIsType
(
"gallery"
);
setTooltip
({});
return
false
;
}
else
if
(
practice
.
length
>
0
)
{
const
title
=
$
(
practice
).
data
(
"title"
);
const
bookid
=
$
(
practice
).
data
(
"bookid"
);
const
chapterid
=
$
(
practice
).
data
(
"chapterid"
);
const
practicenum
=
$
(
practice
).
data
(
"practicenum"
);
getTopicList
({
practicenum
,
chapterid
,
bookid
});
setOpend
(
true
);
setHeadTitle
(
title
);
setIsType
(
"practice"
);
ev
.
preventDefault
();
return
false
;
}
else
if
(
aA
.
length
>
0
)
{
const
href
=
$
(
aA
).
attr
(
"href"
);
let
newLink
=
""
;
if
(
!
/^https*:
\/\/
/
.
test
(
href
))
{
newLink
=
`http://
${
href
}
`
;
}
else
{
newLink
=
href
;
}
window
.
open
(
newLink
);
ev
.
preventDefault
();
return
false
;
}
else
if
(
aExpand
.
length
>
0
)
{
const
title
=
$
(
aExpand
).
data
(
"title"
);
const
name
=
$
(
aExpand
).
data
(
"name"
);
const
position
=
$
(
aExpand
).
data
(
"random"
);
setOpend
(
true
);
setIsType
(
"expand"
);
setHeadTitle
(
name
);
await
getExpandInfo
({
chapterId
:
newId
.
current
,
position
});
}
else
if
(
target
.
nodeName
.
toLowerCase
()
===
"img"
)
{
const
alt
=
$
(
target
).
attr
(
"alt"
);
const
src
=
$
(
target
).
attr
(
"src"
);
setOpend
(
true
);
setIsType
(
"img"
);
setPreviewImg
(
src
);
setHeadTitle
(
alt
?
alt
:
"图片展示"
);
}
setTooltip
({});
setGallery
([]);
});
$
(
".expand-content, .gallery-prview-container"
).
on
(
"click"
,
(
ev
)
=>
{
const
target
=
ev
.
target
;
if
(
target
.
nodeName
.
toLowerCase
()
===
"img"
)
{
const
alt
=
$
(
target
).
attr
(
"alt"
);
const
src
=
$
(
target
).
attr
(
"src"
);
setOpend
(
true
);
setIsType
(
"img"
);
setPreviewImg
(
src
);
setSecondType
(
"扩展图片"
);
}
});
};
useEffect
(()
=>
{
handlerClick
();
},
[
isType
]);
useEffect
(()
=>
{
if
(
bookId
)
{
getChapterTreeList
();
}
},
[
bookId
]);
useEffect
(()
=>
{
if
(
newChapterId
)
{
getChapterId
();
}
},
[
newChapterId
]);
const
onClose
=
()
=>
{
setOpen
(
false
);
};
const
openDrawer
=
(
e
)
=>
{
e
.
preventDefault
();
e
.
stopPropagation
();
setOpen
(
true
);
};
const
handleSelect
=
async
(
checkedKeys
,
info
)
=>
{
if
(
info
.
node
.
children
&&
info
.
node
.
children
.
length
>
0
)
{
const
temp
=
JSON
.
parse
(
JSON
.
stringify
(
expandedKeys
));
if
(
temp
.
includes
(
info
.
node
.
key
))
{
const
newExpands
=
temp
.
filter
((
item
)
=>
item
!==
info
.
node
.
key
);
setExpandedKeys
([...
newExpands
]);
}
else
{
setExpandedKeys
([...
temp
,
info
.
node
.
key
]);
}
}
else
{
setOldTitle
(
info
.
node
.
title
);
setNewChapterId
(
info
.
node
.
key
);
newId
.
current
=
info
.
node
.
key
;
setCheckedKeys
(
checkedKeys
);
setCurrentPage
(
pageMap
[
info
.
node
.
key
]
||
0
);
setOpen
(
false
);
}
};
const
galleryLeft
=
()
=>
{
let
temp
=
galleryIndex
;
if
(
temp
<=
0
)
{
temp
=
0
;
}
else
{
temp
=
temp
-
1
;
}
setGalleryIndex
(
temp
);
};
const
galleryRight
=
()
=>
{
let
temp
=
galleryIndex
;
if
(
temp
>=
gallery
.
length
-
1
)
{
temp
=
gallery
.
length
-
1
;
}
else
{
temp
=
temp
+
1
;
}
setGalleryIndex
(
temp
);
};
return
(
<
div
className=
"priview-modal"
>
{
/* <Spin spinning={loading}> */
}
<
div
className=
"previee-container"
id=
"previee-container"
>
<
div
className=
"chapter-head-title"
>
<
h2
className=
"tree"
>
{
opend
?
(
<
Space
>
<
Button
type=
"text"
onClick=
{
()
=>
{
if
(
secondType
)
{
setSecondType
(
false
);
setPreviewImg
(
null
);
setIsType
(
"expand"
);
return
;
}
setOpend
(
false
);
setIsType
(
""
);
setExpandContent
(
""
);
setTopicList
([]);
setGalleryIndex
(
-
1
);
setNewChapterId
(
newChapterId
);
setPreviewImg
(
""
);
}
}
>
<
LeftOutlined
/>
</
Button
>
{
secondType
?
secondType
:
headTitle
}
</
Space
>
)
:
(
<>
<
span
className=
"no"
>
{
oldTitle
}
</
span
>
<
span
className=
"menu"
>
<
BarsOutlined
style=
{
{
fontSize
:
22
}
}
onClick=
{
()
=>
setOpen
(
true
)
}
/>
</
span
>
</>
)
}
</
h2
>
</
div
>
<
div
className=
"preview-content-it"
>
<
div
className=
"preview-content-show"
>
<
Spin
spinning=
{
loading
}
wrapperClassName=
"chapter-loading"
>
<
div
className=
"preview-content-html"
dangerouslySetInnerHTML=
{
{
__html
:
prviewHtml
}
}
></
div
>
</
Spin
>
{
currentPage
>
0
&&
(
<
div
className=
"preview-page-number"
>
<
span
>
{
currentPage
}
/
{
totalPages
}
</
span
>
</
div
>
)
}
</
div
>
{
isType
&&
(
<
div
className=
"preview-content-other"
>
{
isType
===
"gallery"
&&
(
<
div
className=
"gallery-prview-container"
>
{
gallery
&&
gallery
.
length
>
0
?
(
<>
<
div
className=
"gallery-img"
>
<
img
src=
{
gallery
[
parseInt
(
galleryIndex
)].
url
}
alt=
{
gallery
[
parseInt
(
galleryIndex
)].
title
}
/>
<
div
className=
"opa prev"
onClick=
{
galleryLeft
}
></
div
>
<
div
className=
"opa next"
onClick=
{
galleryRight
}
></
div
>
</
div
>
<
div
className=
"steps"
>
当前
{
parseInt
(
galleryIndex
)
+
1
}
/
{
gallery
.
length
}
张
</
div
>
<
div
className=
"title"
>
{
gallery
[
parseInt
(
galleryIndex
)].
title
}
</
div
>
<
div
className=
"desc"
>
{
gallery
[
parseInt
(
galleryIndex
)].
desc
}
</
div
>
</>
)
:
(
<>
<
div
className=
"gallery-img noData"
>
画廊数据异常
</
div
>
</>
)
}
</
div
>
)
}
{
isType
===
"practice"
&&
(
<
div
className=
"practice-insert-topic"
>
{
radioList
.
length
>
0
&&
(
<
div
className=
"topic_style"
>
<
h3
>
{
clacTopicText
(
"radio"
)
}
. 单选题
</
h3
>
<
div
className=
"practice-list"
>
{
radioList
.
map
((
item
,
index
)
=>
{
return
(
<
div
className=
"practice-item"
key=
{
index
}
>
<
div
className=
"topic-choose-title"
dangerouslySetInnerHTML=
{
{
__html
:
`<span class="index">${index + 1}.</span>${item.titles}`
,
}
}
></
div
>
<
div
className=
"topic_choose-list"
>
{
[
1
,
2
,
3
].
includes
(
parseInt
(
item
.
question_style
))
&&
item
.
question_list
.
map
((
citem
,
cindex
)
=>
{
return
(
<
div
className=
"topic_choose-item"
key=
{
cindex
}
>
<
div
className=
"choose-ico"
>
<
i
className=
{
`ico ${item.question_style === 2 ? "checkbox" : "radio"}`
}
></
i
>
</
div
>
<
div
className=
"topic_content"
dangerouslySetInnerHTML=
{
{
__html
:
`<span class="correct">${correctList[cindex]}.</span> ${citem.option}`
,
}
}
></
div
>
</
div
>
);
})
}
{
item
.
question_style
===
5
&&
(
<
div
>
<
Input
.
TextArea
disabled=
{
true
}
autoSize=
{
{
minRows
:
4
,
maxRows
:
6
}
}
/>
</
div
>
)
}
</
div
>
</
div
>
);
})
}
</
div
>
</
div
>
)
}
{
checkBoxList
.
length
>
0
&&
(
<
div
className=
"topic_style"
>
<
h3
>
{
clacTopicText
(
"checkbox"
,
radioList
)
}
. 多选题
</
h3
>
<
div
className=
"practice-list"
>
{
checkBoxList
.
map
((
item
,
index
)
=>
{
return
(
<
div
className=
"practice-item"
key=
{
index
}
>
<
div
className=
"topic-choose-title"
dangerouslySetInnerHTML=
{
{
__html
:
`<span class="index">${index + 1}.</span>${item.titles}`
,
}
}
></
div
>
<
div
className=
"topic_choose-list"
>
{
[
1
,
2
,
3
].
includes
(
parseInt
(
item
.
question_style
))
&&
item
.
question_list
.
map
((
citem
,
cindex
)
=>
{
return
(
<
div
className=
"topic_choose-item"
key=
{
cindex
}
>
<
div
className=
"choose-ico"
>
<
i
className=
{
`ico ${item.question_style === 2 ? "checkbox" : "radio"}`
}
></
i
>
</
div
>
<
div
className=
"topic_content"
dangerouslySetInnerHTML=
{
{
__html
:
`<span class="correct">${correctList[cindex]}.</span> ${citem.option}`
,
}
}
></
div
>
</
div
>
);
})
}
{
item
.
question_style
===
5
&&
(
<
div
>
<
Input
.
TextArea
disabled=
{
true
}
autoSize=
{
{
minRows
:
4
,
maxRows
:
6
}
}
/>
</
div
>
)
}
</
div
>
</
div
>
);
})
}
</
div
>
</
div
>
)
}
{
judgeList
.
length
>
0
&&
(
<
div
className=
"topic_style"
>
<
h3
>
{
clacTopicText
(
"judge"
,
radioList
,
checkBoxList
)
}
. 判断题
</
h3
>
<
div
className=
"practice-list"
>
{
judgeList
.
map
((
item
,
index
)
=>
{
return
(
<
div
className=
"practice-item"
key=
{
index
}
>
<
div
className=
"topic-choose-title"
dangerouslySetInnerHTML=
{
{
__html
:
`<span class="index">${index + 1}.</span>${item.titles}`
,
}
}
></
div
>
<
div
className=
"topic_choose-list"
>
{
[
1
,
2
,
3
].
includes
(
parseInt
(
item
.
question_style
))
&&
item
.
question_list
.
map
((
citem
,
cindex
)
=>
{
return
(
<
div
className=
"topic_choose-item"
key=
{
cindex
}
>
<
div
className=
"choose-ico"
>
<
i
className=
{
`ico ${item.question_style === 2 ? "checkbox" : "radio"}`
}
></
i
>
</
div
>
<
div
className=
"topic_content"
dangerouslySetInnerHTML=
{
{
__html
:
`<span class="correct">${correctList[cindex]}.</span> ${citem.option}`
,
}
}
></
div
>
</
div
>
);
})
}
{
item
.
question_style
===
5
&&
(
<
div
>
<
Input
.
TextArea
disabled=
{
true
}
autoSize=
{
{
minRows
:
4
,
maxRows
:
6
}
}
/>
</
div
>
)
}
</
div
>
</
div
>
);
})
}
</
div
>
</
div
>
)
}
{
fillList
.
length
>
0
&&
(
<
div
className=
"topic_style"
>
<
h3
>
{
clacTopicText
(
"fill"
,
radioList
,
checkBoxList
,
judgeList
)
}
. 填空题
</
h3
>
<
div
className=
"practice-list"
>
{
fillList
.
map
((
item
,
index
)
=>
{
return
(
<
div
className=
"practice-item"
key=
{
index
}
>
<
div
className=
"topic-choose-title"
dangerouslySetInnerHTML=
{
{
__html
:
`<span class="index">${index + 1}.</span>${item.titles}`
,
}
}
></
div
>
<
div
className=
"topic_choose-list"
>
{
[
1
,
2
,
3
].
includes
(
parseInt
(
item
.
question_style
))
&&
item
.
question_list
.
map
((
citem
,
cindex
)
=>
{
return
(
<
div
className=
"topic_choose-item"
key=
{
cindex
}
>
<
div
className=
"choose-ico"
>
<
i
className=
{
`ico ${item.question_style === 2 ? "checkbox" : "radio"}`
}
></
i
>
</
div
>
<
div
className=
"topic_content"
dangerouslySetInnerHTML=
{
{
__html
:
`<span class="correct">${correctList[cindex]}.</span> ${citem.option}`
,
}
}
></
div
>
</
div
>
);
})
}
{
item
.
question_style
===
5
&&
(
<
div
>
<
Input
.
TextArea
disabled=
{
true
}
autoSize=
{
{
minRows
:
4
,
maxRows
:
6
}
}
/>
</
div
>
)
}
</
div
>
</
div
>
);
})
}
</
div
>
</
div
>
)
}
{
textareaList
.
length
>
0
&&
(
<
div
className=
"topic_style"
>
<
h3
>
{
clacTopicText
(
"textarea"
,
radioList
,
checkBoxList
,
judgeList
,
fillList
)
}
.
简答题
</
h3
>
<
div
className=
"practice-list"
>
{
textareaList
.
map
((
item
,
index
)
=>
{
return
(
<
div
className=
"practice-item"
key=
{
index
}
>
<
div
className=
"topic-choose-title"
dangerouslySetInnerHTML=
{
{
__html
:
`<span class="index">${index + 1}.</span>${item.titles}`
,
}
}
></
div
>
<
div
className=
"topic_choose-list"
>
{
[
1
,
2
,
3
].
includes
(
parseInt
(
item
.
question_style
))
&&
item
.
question_list
.
map
((
citem
,
cindex
)
=>
{
return
(
<
div
className=
"topic_choose-item"
key=
{
cindex
}
>
<
div
className=
"choose-ico"
>
<
i
className=
{
`ico ${item.question_style === 2 ? "checkbox" : "radio"}`
}
></
i
>
</
div
>
<
div
className=
"topic_content"
dangerouslySetInnerHTML=
{
{
__html
:
`${correctList[cindex]}. ${citem.option}`
,
}
}
></
div
>
</
div
>
);
})
}
{
item
.
question_style
===
5
&&
(
<
div
>
<
Input
.
TextArea
disabled=
{
true
}
autoSize=
{
{
minRows
:
4
,
maxRows
:
6
}
}
/>
</
div
>
)
}
</
div
>
</
div
>
);
})
}
</
div
>
</
div
>
)
}
</
div
>
)
}
{
isType
===
"expand"
&&
(
<
div
className=
"expand-container"
>
<
div
className=
"expand-content"
dangerouslySetInnerHTML=
{
{
__html
:
expandContent
}
}
></
div
>
</
div
>
)
}
{
isType
===
"img"
&&
(
<
div
className=
"img-preview"
>
<
Image
width=
{
430
}
src=
{
previewImg
}
/>
</
div
>
)
}
</
div
>
)
}
<
div
className=
"tooltip"
style=
{
toolTip
.
style
}
>
<
div
className=
{
`square ${
toolTip.inSide === "top"
? "square_top"
: toolTip.inSide === "bottom"
? "square_bottom"
: ""
}`
}
style=
{
toolTip
.
squareStyle
}
></
div
>
<
div
className=
"tooltip-content-container"
>
<
div
className=
"tooltip-content"
>
<
div
className=
"content"
>
<
p
>
{
toolTip
.
content
}
</
p
>
</
div
>
{
toolTip
.
link
&&
(
<
div
className=
"content-opa"
>
<
a
href=
{
toolTip
.
link
}
target=
"_blank"
rel=
"noreferrer"
className=
"c-link"
></
a
>
{
/* <a
href={`https://baike.baidu.com/item/${toolTip?.title}`}
target='_blank'
rel='noreferrer'
>
<img src={baike} alt='baike' />
</a> */
}
</
div
>
)
}
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
<
Drawer
onClose=
{
onClose
}
open=
{
open
}
getContainer=
{
false
}
placement=
"left"
classNames=
{
{
header
:
"priview-drawer-header"
,
body
:
"priview-drawer-body"
}
}
width=
"300"
title=
"章节列表"
rootClassName=
"priview-drawer-container"
>
<
Tree
className=
"draggable-tree"
onSelect=
{
handleSelect
}
defaultExpandedKeys=
{
expandedKeys
}
expandedKeys=
{
expandedKeys
}
defaultExpandAll
blockNode
treeData=
{
gData
}
titleRender=
{
(
nodeData
)
=>
{
const
page
=
pageMap
[
nodeData
.
key
];
return
(
<
span
className=
"tree-node-title"
>
<
span
className=
"tree-node-name"
>
{
nodeData
.
title
}
</
span
>
{
page
!=
null
&&
<
span
className=
"tree-node-page"
>
P
{
page
}
</
span
>
}
</
span
>
);
}
}
/>
</
Drawer
>
{
/* </Spin> */
}
</
div
>
);
};
export
default
forwardRef
(
PreviewScreen
);
apps/admin/src/modules/books/audit/routes.tsx
浏览文件 @
77cf6ebc
import
{
Navigate
,
Outlet
,
useRoutes
}
from
"react-router"
;
import
AuditDetailView
from
"./views/AuditDetailView"
;
import
AuditListView
from
"./views/AuditListView"
;
import
"
@/pages/books/audit/index
.less"
;
import
"
./audit
.less"
;
function
Layout
()
{
return
<
Outlet
/>;
}
export
default
function
AuditRoutes
()
{
return
useRoutes
([{
Component
:
Layout
,
children
:
[{
index
:
true
,
Component
:
AuditListView
},
{
path
:
"dataset"
,
Component
:
AuditListView
},
{
path
:
"detail"
,
Component
:
AuditDetailView
},
{
path
:
"*"
,
element
:
<
Navigate
to=
"."
replace
/>
}]
}]);
}
apps/admin/src/modules/books/audit/views/AuditDetailView.tsx
浏览文件 @
77cf6ebc
...
...
@@ -4,7 +4,7 @@ import { useLocation, useNavigate } from "react-router";
import
{
useSelector
}
from
"react-redux"
;
import
{
addExamine
,
getExamineList
}
from
"../api"
;
import
AuditHistory
from
"../components/AuditHistory"
;
import
"
@/pages/books/audit/index
.less"
;
import
"
../audit
.less"
;
export
default
function
AuditDetailView
()
{
const
location
=
useLocation
();
const
navigate
=
useNavigate
();
const
id
=
(
location
.
state
as
{
id
?:
string
|
number
}
|
null
)?.
id
;
const
{
operationPermissionsList
=
[]
}
=
useSelector
((
state
:
any
)
=>
state
.
user
);
const
[
form
]
=
Form
.
useForm
();
const
[
history
,
setHistory
]
=
useState
<
any
[]
>
([]);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
forceOpen
,
setForceOpen
]
=
useState
(
false
);
const
[
forceMessage
,
setForceMessage
]
=
useState
(
""
);
const
loadHistory
=
async
()
=>
{
if
(
!
id
)
return
;
const
data
:
any
=
await
getExamineList
(
id
);
if
(
data
)
setHistory
(
data
);
};
...
...
apps/admin/src/modules/books/management/api.ts
浏览文件 @
77cf6ebc
...
...
@@ -3,6 +3,7 @@ import { message } from "antd";
import
qs
from
"qs"
;
import
request
from
"@/utils/request.js"
;
import
legacyAxios
from
"@/utils/axios"
;
import
type
{
ManagementQuery
}
from
"./query"
;
...
...
@@ -80,6 +81,19 @@ export function deleteManagementBook(id: number) {
return
mutate
(
"/book/teacher/del"
,
{
id
});
}
// 编辑页仍沿用旧接口封装的返回值形状;保留在管理模块中,避免页面反向依赖旧目录。
export
function
getBook
(
params
:
ApiParams
)
{
return
legacyAxios
.
post
(
"/api/book/teacher/getInfoById"
,
params
);
}
export
function
addBook
(
params
:
ApiParams
)
{
return
legacyAxios
.
post
(
"/api/book/teacher/add"
,
params
);
}
export
function
updateBook
(
params
:
ApiParams
)
{
return
legacyAxios
.
post
(
"/api/book/teacher/edit"
,
params
);
}
export
function
exportManagementBook
(
id
:
number
)
{
return
getPayload
<
ExportBookResult
>
(
"/book/teacher/export"
,
{
id
});
}
...
...
apps/admin/src/modules/books/management/chapters/api.js
0 → 100644
浏览文件 @
77cf6ebc
import
axios
from
"@/utils/axios"
;
// 获取书籍详情
export
function
getBook
(
data
)
{
return
axios
.
post
(
"/api/book/teacher/getInfoById"
,
data
);
}
// 获取章节列表
export
function
getChapterList
(
data
)
{
return
axios
.
post
(
"/api/book/teacher/chapter/getAllList"
,
data
);
}
// 获取章节详情
export
function
getChapter
(
data
)
{
return
axios
.
post
(
"/api/book/teacher/chapter/getInfoById"
,
data
);
}
// 添加章节
export
function
addChapter
(
data
)
{
return
axios
.
post
(
"/api/book/teacher/chapter/add"
,
data
);
}
// 修改章节
export
function
updateChapter
(
data
)
{
return
axios
.
post
(
"/api/book/teacher/chapter/edit"
,
data
);
}
// 删除章节
export
function
delChapter
(
data
)
{
return
axios
.
post
(
"/api/book/teacher/chapter/del"
,
data
);
}
// 章节排序
export
function
sortChapter
(
data
)
{
return
axios
.
post
(
"/api/book/teacher/chapter/dragOrder"
,
data
);
}
// 获取章节编辑者信息
export
function
getChapterEditors
(
data
)
{
return
axios
.
post
(
"/api/book/teacher/chapter/getEditors"
,
data
);
}
// 保存章节编辑者
export
function
updateChapterEditors
(
data
)
{
return
axios
.
post
(
"/api/book/teacher/chapter/saveEditors"
,
data
);
}
// 知识图谱列表
export
function
getTagList
(
params
)
{
return
axios
.
get
(
"/api/resource/v1/course/course/tag-list"
,
{
params
});
}
// 知识图谱关联书籍章节
export
function
bindTag
(
data
)
{
return
axios
.
post
(
"/api/resource/v1/course/course/tag-add-book"
,
data
);
}
apps/admin/src/modules/books/management/chapters/components/BindKnowledgeGraph.jsx
0 → 100644
浏览文件 @
77cf6ebc
import
{
useState
,
useEffect
,
useCallback
}
from
"react"
;
import
{
Modal
,
Tree
,
App
,
Button
,
Flex
,
Empty
}
from
"antd"
;
import
{
getTagList
,
bindTag
}
from
"../api"
;
function
getIds
(
nodes
)
{
let
ids
=
[];
nodes
.
forEach
((
node
)
=>
{
ids
.
push
(
node
.
id
);
if
(
node
.
children
&&
node
.
children
.
length
>
0
)
{
ids
=
ids
.
concat
(
getIds
(
node
.
children
));
}
});
return
ids
;
}
const
BindKnowledgeGraph
=
({
chapter
=
{},
...
props
})
=>
{
const
{
message
}
=
App
.
useApp
();
const
[
treeData
,
setTreeData
]
=
useState
([]);
const
[
expandedKeys
,
setExpandedKeys
]
=
useState
([]);
const
fetchList
=
useCallback
(()
=>
{
getTagList
({
type
:
2
,
parent_id
:
chapter
.
book_id
,
chapter_id
:
chapter
.
id
}).
then
((
res
)
=>
{
setTreeData
(
res
.
data
);
setExpandedKeys
(
getIds
(
res
.
data
));
});
},
[
chapter
]);
useEffect
(()
=>
{
fetchList
();
},
[
fetchList
]);
const
toggleBind
=
async
(
node
)
=>
{
await
bindTag
({
tag_id
:
node
.
id
,
book_id
:
chapter
.
book_id
,
chapter_id
:
chapter
.
id
,
option_type
:
node
.
is_checked
?
"delete"
:
"add"
,
});
message
.
success
(
"操作成功"
);
fetchList
();
};
const
titleRender
=
(
node
)
=>
{
return
(
<
Flex
justify=
"space-between"
>
<
span
>
{
node
.
name
}
</
span
>
<
Button
color=
"primary"
variant=
"link"
size=
"small"
onClick=
{
()
=>
toggleBind
(
node
)
}
>
{
node
.
is_checked
?
"取消关联"
:
"关联"
}
</
Button
>
</
Flex
>
);
};
return
(
<
Modal
title=
"关联知识图谱"
centered
{
...
props
}
footer=
{
null
}
>
{
treeData
.
length
>
0
?
(
<
Tree
blockNode
selectable=
{
false
}
treeData=
{
treeData
}
fieldNames=
{
{
title
:
"name"
,
key
:
"id"
}
}
expandedKeys=
{
expandedKeys
}
titleRender=
{
titleRender
}
onExpand=
{
setExpandedKeys
}
/>
)
:
(
<
Empty
image=
{
Empty
.
PRESENTED_IMAGE_SIMPLE
}
description=
"暂无知识图谱"
/>
)
}
</
Modal
>
);
};
export
default
BindKnowledgeGraph
;
apps/admin/src/modules/books/management/chapters/components/EditChapterEditors.jsx
0 → 100644
浏览文件 @
77cf6ebc
import
{
useState
,
useEffect
}
from
"react"
;
import
{
Modal
,
Form
,
Radio
,
Space
,
App
}
from
"antd"
;
import
{
getChapterEditors
,
updateChapterEditors
}
from
"../api"
;
const
EditChapterEditors
=
({
chapter
=
{},
onChange
,
...
props
})
=>
{
const
{
message
}
=
App
.
useApp
();
const
[
editors
,
setEditors
]
=
useState
([]);
const
[
value
,
setValue
]
=
useState
(
""
);
useEffect
(()
=>
{
getChapterEditors
({
book_id
:
chapter
.
book_id
,
chapter_id
:
chapter
.
id
}).
then
((
res
)
=>
{
setEditors
(
res
.
data
.
editors
||
[]);
const
value
=
res
.
data
.
selected_editor_ids
[
0
]
||
""
;
setValue
(
value
);
});
},
[
chapter
]);
const
handleSubmit
=
async
()
=>
{
await
updateChapterEditors
({
book_id
:
chapter
.
book_id
,
chapter_id
:
chapter
.
id
,
editor_id
:
value
,
});
message
.
success
(
"设置成功"
);
onChange
&&
onChange
();
};
return
(
<
Modal
title=
"设置章节编写者"
centered
{
...
props
}
onOk=
{
handleSubmit
}
>
<
Form
labelCol=
{
{
span
:
8
}
}
>
<
Form
.
Item
label=
"当前章节"
>
{
chapter
.
title
}
</
Form
.
Item
>
<
Form
.
Item
label=
"编写者"
>
<
Radio
.
Group
value=
{
value
}
onChange=
{
(
e
)
=>
setValue
(
e
.
target
.
value
)
}
>
<
Space
direction=
"vertical"
>
{
editors
.
map
((
item
)
=>
{
return
(
<
Radio
value=
{
item
.
editor_id
}
key=
{
item
.
editor_id
}
>
{
item
.
real_name
}
(
{
item
.
tel
}
)
</
Radio
>
);
})
}
</
Space
>
</
Radio
.
Group
>
</
Form
.
Item
>
</
Form
>
</
Modal
>
);
};
export
default
EditChapterEditors
;
apps/admin/src/modules/books/management/chapters/components/form-chapter-title.jsx
0 → 100644
浏览文件 @
77cf6ebc
import
{
useState
,
useEffect
,
useRef
}
from
"react"
;
import
{
Form
,
Button
,
Space
,
Input
}
from
"antd"
;
import
{
setTreeChapter
}
from
"@/store/modules/user"
;
import
{
useDispatch
}
from
"react-redux"
;
import
{
chapterEdit
,
chapterAdd
,
sectionAdd
}
from
"../request"
;
const
EditChapterTitle
=
(
props
)
=>
{
const
{
editValue
,
setEditValue
,
editKey
,
setEditKey
,
parentId
,
bookId
,
getChapterTreeList
,
expandedKeys
,
setExpandedKeys
,
editorLoading
,
newChapterChange
,
}
=
props
;
const
dispatch
=
useDispatch
();
const
[
form
]
=
Form
.
useForm
();
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
initValues
,
setInitValues
]
=
useState
({
title
:
editValue
});
const
inputRef
=
useRef
();
useEffect
(()
=>
{
if
(
inputRef
.
current
)
{
inputRef
.
current
.
focus
();
}
},
[]);
useEffect
(()
=>
{
if
(
inputRef
.
current
)
{
inputRef
.
current
.
focus
();
}
},
[
editorLoading
]);
useEffect
(()
=>
{
form
.
setFieldsValue
({
title
:
editValue
});
setInitValues
({
title
:
editValue
});
},
[
editValue
]);
const
onFinish
=
async
(
values
)
=>
{
setLoading
(
true
);
let
data
=
null
;
if
(
parentId
)
{
data
=
await
chapterAdd
({
pid
:
parentId
===
-
1
?
0
:
parentId
,
book_id
:
bookId
,
name
:
values
.
title
,
});
}
else
{
data
=
await
chapterEdit
({
id
:
editKey
,
name
:
values
.
title
});
}
await
dispatch
(
setTreeChapter
({
saveBookId
:
bookId
,
saveChapterId
:
data
.
id
}));
if
(
data
)
{
if
(
parentId
)
{
await
sectionAdd
({
book_id
:
bookId
,
chapter_id
:
data
.
id
,
content
:
""
,
word_count
:
0
});
}
setEditKey
(
false
);
setEditValue
(
null
);
await
getChapterTreeList
();
if
(
parentId
)
{
let
temp
=
[...
expandedKeys
,
parentId
];
setExpandedKeys
(
temp
);
await
newChapterChange
(
data
.
id
,
values
.
title
);
}
}
setLoading
(
false
);
};
return
(
<
Form
layout=
"vertical"
form=
{
form
}
onFinish=
{
onFinish
}
initialValues=
{
initValues
}
>
<
Form
.
Item
label=
"章节标题"
name=
"title"
rules=
{
[{
required
:
true
,
message
:
"请输入章节标题"
}]
}
extra=
"最多30个字符"
>
<
Input
ref=
{
inputRef
}
autoFocus
maxLength=
{
30
}
placeholder=
""
allowClear
/>
</
Form
.
Item
>
<
Form
.
Item
className=
"editor-form-buttons"
wrapperCol=
{
{
offset
:
9
,
span
:
16
}
}
>
<
Space
size=
{
20
}
>
<
Button
type=
"default"
disabled=
{
loading
}
onClick=
{
()
=>
setEditKey
(
false
)
}
>
取消
</
Button
>
<
Button
type=
"primary"
loading=
{
loading
}
htmlType=
"submit"
>
确认
</
Button
>
</
Space
>
</
Form
.
Item
>
</
Form
>
);
};
export
default
EditChapterTitle
;
apps/admin/src/modules/books/management/chapters/index.jsx
0 → 100644
浏览文件 @
77cf6ebc
import
{
useState
,
useEffect
,
useRef
,
useCallback
}
from
"react"
;
import
{
Button
,
Row
,
Col
,
Descriptions
,
Tree
,
Dropdown
,
Modal
,
Spin
,
App
}
from
"antd"
;
import
{
EllipsisOutlined
,
DiffOutlined
,
MenuFoldOutlined
,
MenuUnfoldOutlined
,
}
from
"@ant-design/icons"
;
import
WangEditorCustomer
from
"@/common/wangeditor-customer"
;
import
{
useLocation
,
useNavigate
}
from
"react-router"
;
import
{
useDispatch
,
useSelector
}
from
"react-redux"
;
import
{
setAutosaveTime
}
from
"@/store/modules/editor"
;
import
{
setTreeChapter
}
from
"@/store/modules/user"
;
import
EditChapterTitle
from
"./components/form-chapter-title"
;
import
EditChapterEditors
from
"./components/EditChapterEditors"
;
import
BindKnowledgeGraph
from
"./components/BindKnowledgeGraph"
;
import
{
get
}
from
"lodash-es"
;
import
md5
from
"js-md5"
;
import
{
convertToAntdTreeData
,
findTreeElementByKey
,
findFirstNotHasChildren
,
findParentLevelOne
,
findTreeToIndex
,
findNodeById
,
}
from
"@/utils/common"
;
import
{
getAllList
,
getInfoByChapterId
,
sectionEdit
,
chapterDel
,
dragOrder
}
from
"./request"
;
import
"./index.less"
;
const
Examine
=
()
=>
{
const
{
modal
}
=
App
.
useApp
();
const
location
=
useLocation
();
const
id
=
get
(
location
,
"state.id"
,
""
);
const
isAdminOrCreator
=
get
(
location
,
"state.isAdminOrCreator"
,
false
);
const
{
treeChapter
,
userInfo
}
=
useSelector
((
state
)
=>
state
.
user
);
const
navigate
=
useNavigate
();
const
dispatch
=
useDispatch
();
const
[
gData
,
setGData
]
=
useState
([]);
const
[
chapterId
,
setChapterId
]
=
useState
(
0
);
const
[
bookId
,
setBookId
]
=
useState
(
0
);
// 树节点设置
const
[
expandedKeys
,
setExpandedKeys
]
=
useState
([]);
const
[
checkedKeys
,
setCheckedKeys
]
=
useState
([]);
const
[
nowTitle
,
setNowTitle
]
=
useState
(
""
);
// 编辑操作
const
[
isCollapse
,
setisCollapse
]
=
useState
(
false
);
const
[
sidebarWidth
,
setSidebarWidth
]
=
useState
(
280
);
const
isResizing
=
useRef
(
false
);
const
sidebarRef
=
useRef
(
null
);
const
[
editKey
,
setEditKey
]
=
useState
(
null
);
const
[
parentId
,
setParentId
]
=
useState
(
null
);
const
[
editValue
,
setEditValue
]
=
useState
(
""
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
contentMd5
,
setContentMd5
]
=
useState
(
""
);
// 设置编者
const
[
currentChapter
,
setCurrentChapter
]
=
useState
(
null
);
const
[
editChapterEditorsIsOpen
,
setEditChapterEditorsIsOpen
]
=
useState
(
false
);
const
onChapterEditorsChange
=
()
=>
{
setEditChapterEditorsIsOpen
(
false
);
};
// 关联知识图谱
const
[
bindKnowledgeGraphIsOpen
,
setBindKnowledgeGraphIsOpen
]
=
useState
(
false
);
// 拖拽改变侧边栏宽度
const
handleMouseDown
=
useCallback
(
(
e
)
=>
{
e
.
preventDefault
();
isResizing
.
current
=
true
;
document
.
body
.
style
.
cursor
=
"col-resize"
;
document
.
body
.
style
.
userSelect
=
"none"
;
const
startX
=
e
.
clientX
;
const
startWidth
=
sidebarWidth
;
const
handleMouseMove
=
(
e
)
=>
{
if
(
!
isResizing
.
current
)
return
;
const
delta
=
e
.
clientX
-
startX
;
const
newWidth
=
Math
.
min
(
Math
.
max
(
startWidth
+
delta
,
200
),
600
);
setSidebarWidth
(
newWidth
);
};
const
handleMouseUp
=
()
=>
{
isResizing
.
current
=
false
;
document
.
body
.
style
.
cursor
=
""
;
document
.
body
.
style
.
userSelect
=
""
;
document
.
removeEventListener
(
"mousemove"
,
handleMouseMove
);
document
.
removeEventListener
(
"mouseup"
,
handleMouseUp
);
};
document
.
addEventListener
(
"mousemove"
,
handleMouseMove
);
document
.
addEventListener
(
"mouseup"
,
handleMouseUp
);
},
[
sidebarWidth
],
);
// 编辑器内容
const
editorRef
=
useRef
();
const
saveInterRef
=
useRef
();
const
[
html
,
setHtml
]
=
useState
(
""
);
const
[
contentId
,
setContentId
]
=
useState
(
false
);
// 获取目录结构
const
getChapterTreeList
=
async
()
=>
{
setLoading
(
true
);
const
{
data
=
[],
code
}
=
await
getAllList
({
book_id
:
id
});
if
(
code
!==
200
)
{
setLoading
(
false
);
navigate
(
"/books/management/list"
,
{
replace
:
true
});
return
;
}
if
(
data
.
length
===
0
)
{
setLoading
(
false
);
return
;
}
const
uniqueBookList
=
[...
new
Set
(
data
.
map
((
item
)
=>
item
.
book_id
))][
0
];
setBookId
(
uniqueBookList
);
const
arr
=
convertToAntdTreeData
(
data
,
"name"
);
setGData
(
arr
);
const
allKeys
=
getAllNodeKeys
(
arr
);
setExpandedKeys
(
allKeys
);
const
first
=
findFirstNotHasChildren
(
arr
);
setNowTitle
(
first
.
title
);
if
(
treeChapter
&&
Object
.
entries
(
treeChapter
).
length
>
0
)
{
if
(
parseInt
(
treeChapter
.
saveBookId
)
===
parseInt
(
id
))
{
const
childInKey
=
findTreeElementByKey
(
arr
,
"key"
,
treeChapter
.
saveChapterId
);
if
(
childInKey
)
{
setChapterId
(
childInKey
.
key
);
setCheckedKeys
([
childInKey
.
key
]);
setNowTitle
(
childInKey
.
title
);
}
else
{
setChapterId
(
first
.
key
);
setCheckedKeys
([
first
.
key
]);
setNowTitle
(
first
.
title
);
}
}
else
{
setChapterId
(
first
.
key
);
setCheckedKeys
([
first
.
key
]);
setNowTitle
(
first
.
title
);
await
dispatch
(
setTreeChapter
({
saveBookId
:
id
,
saveChapterId
:
first
.
key
}));
}
}
else
{
setChapterId
(
first
.
key
);
setCheckedKeys
([
first
.
key
]);
setNowTitle
(
first
.
title
);
}
setLoading
(
false
);
};
const
newChapterChange
=
(
id
,
title
)
=>
{
setChapterId
(
parseInt
(
id
));
setCheckedKeys
([
parseInt
(
id
)]);
setNowTitle
(
title
);
};
// 递归函数,获取所有节点的key
const
getAllNodeKeys
=
(
nodes
)
=>
{
let
keys
=
[];
nodes
.
forEach
((
node
)
=>
{
keys
.
push
(
node
.
key
);
if
(
node
.
children
&&
node
.
children
.
length
>
0
)
{
keys
=
keys
.
concat
(
getAllNodeKeys
(
node
.
children
));
}
});
return
keys
;
};
const
[
disabled
,
setDisabled
]
=
useState
(
false
);
// 获取内容
const
getChapterVal
=
async
()
=>
{
setLoading
(
true
);
const
data
=
await
getInfoByChapterId
({
chapter_id
:
chapterId
});
setDisabled
(
!
data
.
selected_editor_ids
.
includes
(
userInfo
.
id
)
&&
!
isAdminOrCreator
);
dispatch
(
setTreeChapter
({
saveBookId
:
id
,
saveChapterId
:
chapterId
,
...
data
}));
const
{
content
,
id
:
cId
}
=
data
;
if
(
content
)
{
setContentMd5
(
md5
(
content
));
}
setHtml
(
content
);
setContentId
(
cId
);
if
(
editorRef
.
current
&&
editorRef
.
current
.
editor
)
{
editorRef
.
current
.
editor
.
setHtml
(
content
);
}
setLoading
(
false
);
};
const
onExpand
=
(
expandedKeys
)
=>
{
setExpandedKeys
(
expandedKeys
);
};
const
handleSelect
=
async
(
selectedKeys
,
info
)
=>
{
if
(
!
info
.
node
)
return
;
const
{
key
,
title
}
=
info
.
node
;
// 保持选中状态,防止重复点击导致选中状态消失
setCheckedKeys
([
key
]);
// 如果点击的是当前已经选中的章节,且没有发生章节切换,则直接返回
if
(
key
===
chapterId
)
{
return
;
}
setLoading
(
true
);
// 切换章节前清理保存定时器并执行一次保存
clearInterval
(
saveInterRef
.
current
);
await
saveContent
();
// 清理旧内容
if
(
editorRef
.
current
&&
editorRef
.
current
.
editor
)
{
editorRef
.
current
.
editor
.
clear
();
}
// 设置新章节 ID,触发 useEffect 加载数据
setChapterId
(
key
);
setNowTitle
(
title
);
setLoading
(
false
);
};
useEffect
(()
=>
{
getChapterTreeList
();
},
[]);
useEffect
(()
=>
{
if
(
chapterId
)
{
getChapterVal
();
}
},
[
chapterId
]);
// 保存方法
const
saveContent
=
async
()
=>
{
if
((
contentId
,
editorRef
.
current
,
editorRef
.
current
.
editor
,
!
loading
))
{
const
contentHtml
=
editorRef
.
current
.
editor
.
getHtml
();
const
newMd5
=
md5
(
contentHtml
);
clearInterval
(
saveInterRef
.
current
);
const
word_count
=
editorRef
.
current
.
editor
.
getText
().
length
;
// 保存
const
data
=
await
sectionEdit
({
id
:
contentId
,
content
:
contentHtml
,
word_count
,
});
if
(
data
)
{
if
((
data
.
code
&&
data
.
code
===
3000
)
||
data
.
code
===
"3000"
)
{
// navigate('/books/management/list')
return
;
}
else
{
setContentMd5
(
newMd5
);
dispatch
(
setAutosaveTime
(
Date
.
now
()));
}
}
}
};
useEffect
(()
=>
{
if
(
!
loading
&&
contentId
)
{
clearInterval
(
saveInterRef
.
current
);
}
else
{
clearInterval
(
saveInterRef
.
current
);
}
},
[
contentId
,
loading
,
contentMd5
,
html
]);
const
addChapterParent
=
()
=>
{
setEditKey
(
-
1
);
setEditValue
(
""
);
setParentId
(
-
1
);
};
const
delChapter
=
async
(
node
)
=>
{
const
childInKey
=
findParentLevelOne
(
gData
,
"key"
,
node
.
key
);
let
current
=
null
;
if
(
childInKey
&&
childInKey
.
children
&&
childInKey
.
children
.
length
>
0
)
{
const
index
=
findTreeToIndex
(
childInKey
.
children
,
"key"
,
node
.
key
);
if
(
childInKey
.
children
.
length
===
1
)
{
current
=
childInKey
;
}
else
{
// 前面还有
if
(
index
<=
childInKey
.
children
.
length
)
{
if
(
index
===
0
)
{
current
=
childInKey
.
children
[
index
+
1
];
}
else
{
current
=
childInKey
.
children
[
index
-
1
];
}
}
}
}
else
{
const
index
=
findTreeToIndex
(
gData
,
"key"
,
node
.
key
);
let
child
=
null
;
if
(
index
===
0
)
{
child
=
gData
[
index
+
1
];
}
else
{
child
=
gData
[
index
-
1
];
}
if
(
child
&&
child
.
children
&&
child
.
children
.
length
>
0
)
{
current
=
child
.
children
[
0
];
}
else
{
current
=
child
;
}
}
const
data
=
await
chapterDel
({
id
:
node
.
key
});
if
(
data
)
{
await
dispatch
(
setTreeChapter
({
saveBookId
:
id
,
saveChapterId
:
current
?
current
.
key
:
null
}),
);
await
getChapterTreeList
();
}
};
useEffect
(()
=>
{
if
(
gData
&&
gData
.
length
>
0
&&
treeChapter
&&
Object
.
entries
(
treeChapter
).
length
>
0
)
{
if
(
parseInt
(
treeChapter
.
saveBookId
)
===
parseInt
(
id
))
{
const
childInKey
=
findTreeElementByKey
(
gData
,
"key"
,
treeChapter
.
saveChapterId
);
if
(
childInKey
&&
Object
.
entries
(
childInKey
).
length
>
0
)
{
setChapterId
(
parseInt
(
treeChapter
.
saveChapterId
));
setCheckedKeys
([
parseInt
(
treeChapter
.
saveChapterId
)]);
setNowTitle
(
childInKey
.
title
);
}
}
}
},
[
gData
,
treeChapter
]);
let
chapterMenuItems
=
[
{
key
:
"1"
,
label
:
"展开全部"
},
{
key
:
"2"
,
label
:
"添加子节"
},
{
key
:
"7"
,
label
:
"关联知识图谱"
},
{
key
:
"5"
,
label
:
"设置编者"
},
{
key
:
"6"
,
label
:
"一键AI"
,
children
:
[
{
key
:
"61"
,
label
:
"一键AI创作课件"
,
disabled
:
true
},
{
key
:
"62"
,
label
:
"一键AI创作教案"
,
disabled
:
true
},
],
},
{
key
:
"3"
,
label
:
"编辑"
},
{
key
:
"4"
,
label
:
"删除"
},
];
if
(
!
isAdminOrCreator
)
{
chapterMenuItems
=
[{
key
:
"1"
,
label
:
"展开全部"
}];
}
const
handleMenuClick
=
async
(
e
,
node
)
=>
{
e
.
domEvent
.
stopPropagation
();
setCurrentChapter
(
node
);
if
(
parseInt
(
e
.
key
)
===
1
)
{
// 展开全部
const
ids
=
findNodeById
([
node
],
"key"
,
node
.
key
);
const
expandedKeysTemp
=
[...
expandedKeys
,
node
.
key
,
...
ids
];
setExpandedKeys
(
expandedKeysTemp
);
}
else
if
(
parseInt
(
e
.
key
)
===
2
)
{
// 添加子节
setEditKey
(
-
1
);
setEditValue
(
""
);
setParentId
(
node
.
key
);
}
else
if
(
parseInt
(
e
.
key
)
===
3
)
{
// 编辑
setEditKey
(
node
.
key
);
setEditValue
(
node
.
title
);
setParentId
(
false
);
}
else
if
(
parseInt
(
e
.
key
)
===
4
)
{
await
modal
.
confirm
({
title
:
"删除确认"
,
content
:
`确认删除子节【
${
node
.
title
}
】?`
,
onOk
()
{
delChapter
(
node
);
},
});
}
else
if
(
e
.
key
==
5
)
{
setEditChapterEditorsIsOpen
(
true
);
}
else
if
(
e
.
key
==
7
)
{
setBindKnowledgeGraphIsOpen
(
true
);
}
};
// 章节名称
const
chapterTitleRender
=
(
node
)
=>
{
return
(
<
div
className=
"chapter-tree-item"
>
<
p
className=
"title"
>
{
node
.
title
}
</
p
>
<
Dropdown
trigger=
{
[
"click"
]
}
menu=
{
{
items
:
chapterMenuItems
,
onClick
:
(
e
)
=>
handleMenuClick
(
e
,
node
)
}
}
>
<
EllipsisOutlined
onClick=
{
(
e
)
=>
e
.
stopPropagation
()
}
/>
</
Dropdown
>
</
div
>
);
};
const
onDrop
=
async
(
info
)
=>
{
const
dropToGap
=
info
.
dropToGap
;
// true 为同级或最大级 false 为子级
const
nowDropNode
=
info
.
dragNode
.
key
;
const
sideDropNode
=
info
.
node
.
key
;
let
data
=
null
;
if
(
dropToGap
)
{
// 同级
const
dragOverGapBottom
=
info
.
node
.
dragOverGapBottom
;
const
dragOver
=
info
.
node
.
dragOver
;
const
dragOverGapTop
=
info
.
node
.
dragOverGapTop
;
if
(
!
dragOverGapTop
&&
!
dragOver
&&
!
dragOverGapBottom
)
{
// 拖到最底部
// 拖拽到最顶层
data
=
await
dragOrder
({
book_id
:
id
,
drag_node
:
nowDropNode
,
position_node
:
sideDropNode
,
drop_type
:
"after"
,
});
}
else
{
if
(
dragOverGapTop
)
{
// 拖拽到最顶层
data
=
await
dragOrder
({
book_id
:
id
,
drag_node
:
nowDropNode
,
position_node
:
sideDropNode
,
drop_type
:
"before"
,
});
}
else
if
(
dragOverGapBottom
)
{
data
=
await
dragOrder
({
book_id
:
id
,
drag_node
:
nowDropNode
,
position_node
:
sideDropNode
,
drop_type
:
"after"
,
});
}
}
}
else
{
// 子级
data
=
await
dragOrder
({
book_id
:
id
,
drag_node
:
nowDropNode
,
position_node
:
sideDropNode
,
drop_type
:
"inner"
,
});
}
if
(
data
)
{
await
getChapterTreeList
();
}
};
return
(
<
div
className=
"examine"
>
<
div
className=
"content-box"
>
<
div
className=
"book-content-row"
>
<
div
ref=
{
sidebarRef
}
className=
"book-content-tree"
style=
{
{
width
:
isCollapse
?
50
:
sidebarWidth
,
minWidth
:
isCollapse
?
50
:
200
,
flexShrink
:
0
,
}
}
>
<
div
className=
"border"
>
{
!
isCollapse
?
(
<>
<
Descriptions
layout=
"vertical"
rootClassName=
"section-left-top"
bordered
items=
{
[
{
key
:
"1"
,
label
:
(
<
Row
gutter=
{
5
}
justify=
{
"space-between"
}
style=
{
{
alignItems
:
"center"
}
}
>
<
Col
>
章节目录
</
Col
>
<
Col
>
{
isAdminOrCreator
&&
(
<
Button
type=
"text"
icon=
{
<
DiffOutlined
/>
}
onClick=
{
addChapterParent
}
></
Button
>
)
}
<
Button
type=
"text"
icon=
{
<
MenuFoldOutlined
/>
}
onClick=
{
()
=>
setisCollapse
(
true
)
}
></
Button
>
</
Col
>
</
Row
>
),
},
]
}
/>
{
gData
&&
gData
.
length
>
0
&&
(
<
Tree
className=
"draggable-tree"
onSelect=
{
handleSelect
}
defaultExpandAll
defaultExpandedKeys=
{
expandedKeys
}
expandedKeys=
{
expandedKeys
}
defaultSelectedKeys=
{
checkedKeys
}
selectedKeys=
{
checkedKeys
}
onDrop=
{
onDrop
}
draggable
blockNode
disabled=
{
loading
}
treeData=
{
gData
}
onExpand=
{
onExpand
}
titleRender=
{
(
nodeData
)
=>
chapterTitleRender
(
nodeData
)
}
/>
)
}
</>
)
:
(
<
Descriptions
layout=
"vertical"
bordered
items=
{
[
{
key
:
"1"
,
label
:
(
<
Row
gutter=
{
5
}
justify=
{
"space-between"
}
style=
{
{
alignItems
:
"center"
}
}
>
<
Col
>
<
Button
type=
"text"
icon=
{
<
MenuUnfoldOutlined
/>
}
onClick=
{
()
=>
setisCollapse
(
false
)
}
></
Button
>
</
Col
>
</
Row
>
),
},
]
}
/>
)
}
</
div
>
</
div
>
{
!
isCollapse
&&
<
div
className=
"resize-handle"
onMouseDown=
{
handleMouseDown
}
/>
}
<
div
className=
"book-content-editor"
style=
{
{
flex
:
1
,
minWidth
:
0
}
}
>
<
div
className=
"editor-right"
>
<
Spin
spinning=
{
loading
}
>
<
WangEditorCustomer
ref=
{
editorRef
}
chapterId=
{
chapterId
}
bookId=
{
id
}
contentId=
{
contentId
}
setHtml=
{
setHtml
}
html=
{
html
}
saveContent=
{
saveContent
}
gData=
{
gData
}
nowTitle=
{
nowTitle
}
disabled=
{
disabled
}
/>
</
Spin
>
</
div
>
</
div
>
</
div
>
</
div
>
<
Modal
open=
{
editKey
}
footer=
{
null
}
centered
destroyOnClose
title=
{
editKey
>
0
?
"修改章节"
:
"新增章节"
}
maskClosable=
{
false
}
mask=
{
false
}
closeIcon=
{
false
}
// 添加这一行以隐藏关闭按钮
onCancel=
{
()
=>
setEditKey
(
false
)
}
classNames=
{
{
wrapper
:
"chapter-title-modal"
,
}
}
>
<
EditChapterTitle
setEditKey=
{
setEditKey
}
editValue=
{
editValue
}
editKey=
{
editKey
}
getChapterTreeList=
{
getChapterTreeList
}
setEditValue=
{
setEditValue
}
parentId=
{
parentId
}
bookId=
{
id
}
expandedKeys=
{
expandedKeys
}
setExpandedKeys=
{
setExpandedKeys
}
editorLoading=
{
loading
}
newChapterChange=
{
newChapterChange
}
/>
</
Modal
>
{
/* 设置章节编写者 */
}
{
currentChapter
&&
editChapterEditorsIsOpen
&&
(
<
EditChapterEditors
open=
{
editChapterEditorsIsOpen
}
chapter=
{
currentChapter
}
onCancel=
{
()
=>
setEditChapterEditorsIsOpen
(
false
)
}
onChange=
{
onChapterEditorsChange
}
></
EditChapterEditors
>
)
}
{
/* 关联知识图谱 */
}
{
currentChapter
&&
bindKnowledgeGraphIsOpen
&&
(
<
BindKnowledgeGraph
open=
{
bindKnowledgeGraphIsOpen
}
chapter=
{
currentChapter
}
onCancel=
{
()
=>
setBindKnowledgeGraphIsOpen
(
false
)
}
onOk=
{
()
=>
setBindKnowledgeGraphIsOpen
(
false
)
}
></
BindKnowledgeGraph
>
)
}
</
div
>
);
};
export
default
Examine
;
apps/admin/src/modules/books/management/chapters/index.less
0 → 100644
浏览文件 @
77cf6ebc
.examine {
height: 100%;
.content-box {
height: 100%;
box-sizing: border-box;
overflow: hidden;
.book-content-row {
height: 100%;
overflow: hidden;
display: flex;
gap: 0;
.book-content-tree {
height: 100%;
overflow: hidden;
transition: width 0s;
}
.book-content-editor {
height: 100%;
overflow: hidden;
}
}
.resize-handle {
width: 6px;
cursor: col-resize;
background: transparent;
position: relative;
flex-shrink: 0;
z-index: 10;
transition: background 0.2s;
&:hover,
&:active {
background: #1890ff;
border-radius: 3px;
}
&::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 2px;
height: 30px;
background: #d9d9d9;
border-radius: 1px;
transition: background 0.2s;
}
&:hover::after,
&:active::after {
background: #fff;
}
}
.ant-descriptions-item-label {
padding: 10px !important;
}
.ant-descriptions-view {
border: none;
}
.border {
border: 1px solid #e5e5e5;
height: 100%;
display: flex;
overflow-x: hidden;
overflow-y: auto;
flex-direction: column;
border-radius: 6px;
.section-left-top {
height: 85px;
}
.ant-descriptions.ant-descriptions-bordered > .ant-descriptions-view .ant-descriptions-row {
border-bottom: 1px solid #e5e5e5;
&:nth-of-type(2) {
display: none;
}
}
}
.editor-right {
height: 100%;
.ant-spin-nested-loading,
.ant-spin-container {
height: 100%;
}
}
}
.draggable-tree {
flex: 1;
height: calc(100vh - 285px);
overflow-y: auto;
overflow-x: hidden;
width: 98%;
&.ant-tree .ant-tree-node-content-wrapper {
width: 100%;
}
.ant-tree-switcher-noop {
// display: none;
width: 10px;
}
&.ant-tree .ant-tree-node-content-wrapper {
overflow: hidden;
}
}
.chapter-tree-item {
display: flex;
justify-content: space-between;
overflow: hidden;
.title {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 10px;
max-height: 24px;
line-height: 24px;
white-space: nowrap;
}
}
}
.chapter-title-modal {
.ant-modal {
.ant-modal-close {
inset-inline-end: 17px;
top: 16px;
}
.ant-modal-content {
padding: 15px 18px;
}
.ant-divider {
margin: 15px 0;
}
}
}
apps/admin/src/modules/books/management/chapters/request.js
0 → 100644
浏览文件 @
77cf6ebc
import
axios
from
"@/utils/request.js"
;
import
qs
from
"qs"
;
import
{
get
}
from
"lodash-es"
;
import
{
message
}
from
"antd"
;
// 用户信息
const
getUserInfo
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/user/user/getUserInfo"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 获取角色已选权限
const
getPowerByRoleId
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/user/role/getPowerByRoleId"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 章节列表
const
getAllList
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/chapter/getAllList"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
{
code
,
data
,
};
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 添加章节
const
chapterAdd
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/chapter/add"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
// message.success('添加成功');
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 章节详情
const
getInfoById
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/chapter/getInfoById"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 修改章节
const
chapterEdit
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/chapter/edit"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
message
.
success
(
msg
);
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 删除章节
const
chapterDel
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/chapter/del"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
message
.
success
(
msg
);
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 拖拽章节排序
const
dragOrder
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/chapter/dragOrder"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
message
.
success
(
msg
);
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 获取章节内容
const
getInfoByChapterId
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/section/getInfoByChapterId"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 添加章节内容
const
sectionAdd
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/section/add"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
message
.
success
(
msg
);
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 修改章节内容
const
sectionEdit
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/section/edit"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
// message.success(msg);
return
true
;
}
else
{
message
.
error
(
msg
);
return
{
code
,
msg
,
data
};
}
}
catch
(
error
)
{
return
false
;
}
};
// 获取章节的题库列表
const
getQuestionList
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/chapter/question/getQuestionList"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 移除课后题
const
questionDel
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/chapter/question/del"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 添加课后题
const
questionAdd
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/chapter/question/add"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 发布审核
const
release
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/release"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 模板列表
const
templateGetList
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/template/getList"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 模板导入
const
templateImport
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/template/import"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 书籍导入
const
teacherImport
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/import"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 保存历史记录
const
getRecordList
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/section/getRecordList"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
export
{
getAllList
,
chapterAdd
,
getInfoById
,
chapterEdit
,
chapterDel
,
dragOrder
,
getInfoByChapterId
,
sectionAdd
,
sectionEdit
,
getQuestionList
,
questionDel
,
questionAdd
,
release
,
templateGetList
,
templateImport
,
teacherImport
,
getRecordList
,
getPowerByRoleId
,
getUserInfo
,
};
apps/admin/src/modules/books/management/components/ManagementAddEdit.jsx
浏览文件 @
77cf6ebc
export
{
default
}
from
"@/pages/books/management/addedit"
;
import
{
useState
,
useEffect
}
from
"react"
;
import
{
Button
,
Form
,
Input
,
Flex
,
message
}
from
"antd"
;
import
WangEditor
from
"@/components/editor"
;
import
UploadImage
from
"@/components/uploadImage"
;
import
{
useLocation
,
useNavigate
}
from
"react-router"
;
import
{
addBook
,
updateBook
,
getBook
}
from
"../api"
;
import
SelectTeacher
from
"@/components/selectTeacher"
;
const
BookAddEdit
=
()
=>
{
const
navigate
=
useNavigate
();
const
location
=
useLocation
();
const
isEdit
=
!!
location
.
state
?.
id
;
const
[
form
]
=
Form
.
useForm
();
const
[
img
,
setImg
]
=
useState
(
""
);
// 编辑器内容
const
[
html
,
setHtml
]
=
useState
(
""
);
// 详情信息
const
fetchInfo
=
async
()
=>
{
const
{
data
}
=
await
getBook
({
id
:
location
.
state
.
id
});
setImg
(
data
.
img
);
setHtml
(
data
.
content
);
const
formFields
=
{
...
data
,
editors
:
data
.
editors_list
.
map
((
item
)
=>
{
return
{
label
:
item
.
real_name
,
value
:
item
.
editor_id
};
}),
};
form
.
setFieldsValue
(
formFields
);
};
useEffect
(()
=>
{
if
(
isEdit
)
fetchInfo
();
},
[
isEdit
]);
const
onFinish
=
async
(
values
)
=>
{
const
editors
=
values
.
editors
?.
map
((
item
)
=>
item
.
value
)
||
[];
const
params
=
{
...
values
,
img
,
editors
};
if
(
editors
.
length
===
0
)
delete
params
.
editors
;
const
res
=
isEdit
?
await
updateBook
({
id
:
location
.
state
.
id
,
...
params
})
:
await
addBook
(
params
);
message
.
success
(
res
.
message
);
navigate
(
-
1
);
};
return
(
<
Form
form=
{
form
}
onFinish=
{
onFinish
}
labelCol=
{
{
span
:
2
}
}
>
<
Form
.
Item
label=
"书籍名称"
name=
"name"
rules=
{
[
{
required
:
true
,
message
:
"请输入书籍名称"
},
{
max
:
30
,
message
:
"最大30字符"
},
]
}
>
<
Input
autoComplete=
"off"
placeholder=
"请输入书籍名称"
style=
{
{
width
:
260
}
}
></
Input
>
</
Form
.
Item
>
<
Form
.
Item
label=
"作者"
name=
"authors"
rules=
{
[
{
required
:
true
,
message
:
"请输入作者"
},
{
max
:
30
,
message
:
"最大30字符"
},
]
}
>
<
Input
autoComplete=
"off"
placeholder=
"请输入作者"
style=
{
{
width
:
260
}
}
></
Input
>
</
Form
.
Item
>
<
Form
.
Item
name=
"img"
label=
"缩略图"
className=
"uploadPar"
>
<
UploadImage
value=
{
img
}
onChange=
{
(
url
)
=>
setImg
(
url
)
}
></
UploadImage
>
</
Form
.
Item
>
<
Form
.
Item
label=
"编辑推荐"
name=
"introduction"
rules=
{
[{
max
:
65535
,
message
:
"最多65535字符"
}]
}
>
<
Input
.
TextArea
placeholder=
"请输入编辑推荐"
autoSize=
{
{
maxRows
:
8
,
minRows
:
5
}
}
style=
{
{
width
:
910
}
}
/>
</
Form
.
Item
>
<
Form
.
Item
label=
"编写者"
name=
"editors"
>
<
SelectTeacher
placeholder=
"请选择编写者"
style=
{
{
width
:
260
}
}
></
SelectTeacher
>
</
Form
.
Item
>
<
Form
.
Item
label=
"简介"
name=
"content"
>
<
WangEditor
value=
{
html
}
onChange=
{
(
editor
)
=>
form
.
setFieldValue
(
"content"
,
editor
.
getHtml
())
}
></
WangEditor
>
</
Form
.
Item
>
<
Form
.
Item
wrapperCol=
{
{
span
:
24
}
}
>
<
Flex
gap=
{
20
}
justify=
"center"
>
<
Button
onClick=
{
()
=>
{
navigate
(
-
1
);
}
}
>
取消
</
Button
>
<
Button
type=
"primary"
htmlType=
"submit"
>
确认
</
Button
>
</
Flex
>
</
Form
.
Item
>
</
Form
>
);
};
export
default
BookAddEdit
;
apps/admin/src/modules/books/management/components/ManagementChapter.jsx
浏览文件 @
77cf6ebc
export
{
default
}
from
"
@/pages/books/section
"
;
export
{
default
}
from
"
../chapters
"
;
apps/admin/src/modules/books/routes.tsx
浏览文件 @
77cf6ebc
...
...
@@ -2,7 +2,7 @@ import { lazy } from "react";
import
{
Navigate
,
useRoutes
}
from
"react-router"
;
import
ModuleBoundary
from
"@/app/ModuleBoundary"
;
const
BooksLayout
=
lazy
(()
=>
import
(
"
@/pages/books/index
"
));
const
BooksLayout
=
lazy
(()
=>
import
(
"
./BooksLayout
"
));
const
ManagementRoutes
=
lazy
(()
=>
import
(
"./management/routes"
));
const
AuditRoutes
=
lazy
(()
=>
import
(
"./audit/routes"
));
const
ClassifyRoutes
=
lazy
(()
=>
import
(
"./classify/routes"
));
...
...
apps/admin/src/modules/books/sale/api.ts
浏览文件 @
77cf6ebc
...
...
@@ -8,3 +8,24 @@ async function post(path: string, data: Record<string, unknown>, notify = false)
export
const
getSaleList
=
(
query
:
SaleQuery
)
=>
post
(
"/book/book/getList"
,
query
);
export
const
changeSaleStatus
=
(
id
:
string
|
number
,
status
:
string
)
=>
post
(
"/book/book/changeStatus"
,
{
id
,
status
},
true
);
export
const
getSaleDiscussionList
=
(
query
:
SaleDiscussionQuery
)
=>
post
(
"/comment/comment/getCommentList"
,
query
);
export
const
getListAll
=
(
data
:
Record
<
string
,
unknown
>
)
=>
post
(
"/book/chapter/getListAll"
,
data
);
export
const
getInfoById
=
(
data
:
Record
<
string
,
unknown
>
)
=>
post
(
"/book/book/getInfoById"
,
data
);
export
const
publisherList
=
(
data
:
Record
<
string
,
unknown
>
)
=>
post
(
"/book/publisher/getListAll"
,
data
);
export
const
publisherAdd
=
(
data
:
Record
<
string
,
unknown
>
)
=>
post
(
"/book/publisher/add"
,
data
,
true
);
export
const
editBook
=
(
data
:
Record
<
string
,
unknown
>
)
=>
post
(
"/book/book/edit"
,
data
,
true
);
export
const
getCommentInfo
=
(
data
:
Record
<
string
,
unknown
>
)
=>
post
(
"/comment/comment/getCommentInfo"
,
data
);
export
const
commentDel
=
async
(
data
:
Record
<
string
,
unknown
>
)
=>
{
const
result
=
await
post
(
"/comment/comment/del"
,
data
,
true
);
return
result
!==
false
;
};
export
const
commentReply
=
async
(
data
:
Record
<
string
,
unknown
>
)
=>
{
const
result
=
await
post
(
"/comment/comment/reply"
,
data
,
true
);
return
result
!==
false
;
};
apps/admin/src/modules/books/sale/components/SaleDiscussionDetail.jsx
浏览文件 @
77cf6ebc
export
{
default
}
from
"@/pages/books/sale/discussDetail"
;
import
{
Card
,
Input
,
Button
,
Space
,
Row
,
Col
,
Modal
,
Form
}
from
"antd"
;
import
{
DeleteOutlined
,
FacebookFilled
,
MessageOutlined
}
from
"@ant-design/icons"
;
import
"./SaleDiscussionDetail.less"
;
import
User
from
"@/assets/images/icon/user.png"
;
import
{
useState
,
useEffect
}
from
"react"
;
import
{
useLocation
}
from
"react-router"
;
import
{
getCommentInfo
,
commentReply
,
commentDel
}
from
"../api"
;
const
{
TextArea
}
=
Input
;
const
DiscussDetail
=
()
=>
{
const
[
showReply
,
setShowReply
]
=
useState
(
false
);
const
[
delMoadal
,
setDelModal
]
=
useState
(
false
);
const
{
state
:
{
id
,
name
,
book_id
},
}
=
useLocation
();
const
[
form
]
=
Form
.
useForm
();
const
[
data
,
setData
]
=
useState
({});
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
commentId
,
setcommentId
]
=
useState
(
0
);
// 获取列表
const
init
=
async
()
=>
{
setLoading
(
true
);
const
data
=
await
getCommentInfo
({
id
,
book_id
});
console
.
log
(
data
);
setData
(
data
);
setLoading
(
false
);
};
useEffect
(()
=>
{
init
();
},
[]);
useEffect
(()
=>
{
if
(
!
showReply
)
{
form
.
setFieldsValue
({
content
:
""
});
form
.
resetFields
();
}
},
[
showReply
]);
// 显示回复弹框
const
showReplyFn
=
(
id
)
=>
{
setcommentId
(
id
);
setShowReply
(
true
);
};
// 点击确认回复
const
handleReply
=
async
(
obj
)
=>
{
const
bool
=
await
commentReply
({
...
obj
,
id
:
commentId
,
book_id
});
if
(
!
bool
)
return
;
setShowReply
(
false
);
init
();
};
const
handleDel
=
async
()
=>
{
const
bool
=
await
commentDel
({
id
:
commentId
,
book_id
});
if
(
!
bool
)
return
;
setDelModal
(
false
);
init
();
};
return
(
<
div
className=
"DiscussDetail"
>
<
Modal
open=
{
showReply
}
onCancel=
{
()
=>
{
setShowReply
(
false
);
}
}
maskClosable=
{
false
}
footer=
{
null
}
centered
closeIcon=
{
null
}
keyboard=
{
false
}
>
<
br
/>
<
Form
form=
{
form
}
onFinish=
{
handleReply
}
>
<
Form
.
Item
label=
"内容"
name=
"content"
rules=
{
[{
required
:
true
,
message
:
"请输入内容"
}]
}
>
<
TextArea
placeholder=
"请输入内容"
autoSize=
{
{
minRows
:
3
,
maxRows
:
5
,
}
}
></
TextArea
>
</
Form
.
Item
>
<
Form
.
Item
>
<
Row
gutter=
{
20
}
justify=
{
"center"
}
>
<
Col
>
<
Button
onClick=
{
()
=>
setShowReply
(
false
)
}
>
取消
</
Button
>
</
Col
>
<
Col
>
<
Button
type=
"primary"
htmlType=
"submit"
>
确认
</
Button
>
</
Col
>
</
Row
>
</
Form
.
Item
>
</
Form
>
</
Modal
>
<
Modal
mask=
{
false
}
centered
open=
{
delMoadal
}
onOk=
{
handleDel
}
onCancel=
{
()
=>
setDelModal
(
false
)
}
footer=
{
(
_
,
{
OkBtn
,
CancelBtn
})
=>
{
return
(
<
Row
justify=
{
"center"
}
className=
"delModal"
>
<
Space
>
<
CancelBtn
/>
<
OkBtn
/>
</
Space
>
</
Row
>
);
}
}
okText=
"确认"
>
<
p
style=
{
{
textAlign
:
"center"
,
padding
:
"50px 0 30px"
,
fontSize
:
16
}
}
>
评论将永久删除,是否继续?
</
p
>
</
Modal
>
<
Card
className=
"card"
title=
{
`书名:《${name}》`
}
loading=
{
loading
}
>
<
div
className=
"discussBox"
>
<
Row
className=
"discussItem"
gutter=
{
10
}
>
<
Col
className=
"img"
>
<
img
src=
{
data
.
person_pic
||
User
}
width=
{
28
}
height=
{
28
}
style=
{
{
borderRadius
:
"50%"
,
objectFit
:
"cover"
}
}
/>
</
Col
>
<
Col
className=
"discuss"
flex=
{
1
}
>
<
div
className=
"name"
>
{
data
.
person_name
}
</
div
>
{
/* <div className='content'>{data?.content && data.content[0].content}</div> */
}
<
div
className=
"content"
>
{
data
?.
content
&&
data
.
content
.
map
((
item
,
index
)
=>
{
const
type
=
item
.
types
;
const
value
=
item
.
content
;
if
(
type
===
"audio"
)
{
return
(
<
div
key=
{
index
}
>
<
span
>
<
audio
controls
>
<
source
src=
{
value
}
type=
"audio/mp3"
/>
Your browser does not support the audio element.
</
audio
>
</
span
>
</
div
>
);
}
else
if
(
type
===
"text"
)
{
return
<
span
key=
{
index
}
>
{
value
}
</
span
>;
}
else
if
(
type
===
"image"
)
{
return
<
img
height=
{
100
}
src=
{
value
}
></
img
>;
}
})
}
</
div
>
<
Space
>
<
Button
type=
"text"
className=
"reply"
onClick=
{
()
=>
showReplyFn
(
data
.
id
)
}
icon=
{
<
MessageOutlined
/>
}
>
回复
</
Button
>
<
Button
type=
"text"
className=
"del"
icon=
{
<
DeleteOutlined
/>
}
onClick=
{
()
=>
{
setDelModal
(
true
);
setcommentId
(
data
.
id
);
}
}
>
删除
</
Button
>
</
Space
>
</
Col
>
</
Row
>
{
data
.
child
&&
data
.
child
.
map
((
item
)
=>
(
<
Row
className=
"discussItem reply"
key=
{
item
.
id
}
gutter=
{
10
}
>
<
Col
span=
{
1
}
></
Col
>
<
Col
className=
"img"
>
<
img
src=
{
item
.
person_pic
||
User
}
width=
{
28
}
height=
{
28
}
style=
{
{
borderRadius
:
"50%"
,
objectFit
:
"cover"
}
}
/>
</
Col
>
<
Col
className=
"discuss"
flex=
{
1
}
>
<
div
className=
"name"
>
{
item
.
person_name
}
</
div
>
<
div
className=
"content"
>
{
item
.
content
&&
item
.
content
.
map
((
item
,
index
)
=>
(
<
div
key=
{
index
}
>
{
item
.
types
==
"text"
&&
<
span
>
{
item
.
content
}
</
span
>
}
{
item
.
types
==
"image"
&&
<
img
src=
{
item
.
content
}
></
img
>
}
{
item
.
types
==
"audio"
&&
<
audio
controls
src=
{
item
.
content
}
/>
}
</
div
>
))
}
</
div
>
<
Space
>
<
Button
type=
"text"
className=
"reply"
onClick=
{
()
=>
showReplyFn
(
item
.
id
)
}
icon=
{
<
MessageOutlined
/>
}
>
回复
</
Button
>
<
Button
type=
"text"
className=
"del"
icon=
{
<
DeleteOutlined
/>
}
onClick=
{
()
=>
{
setDelModal
(
true
);
setcommentId
(
item
.
id
);
}
}
>
删除
</
Button
>
</
Space
>
</
Col
>
</
Row
>
))
}
</
div
>
{
/* <div className="discussCreate">
<Row gutter={10}>
<Col><div className="img"><img src={User} width={28} height={28} /></div></Col>
<Col><Input autoComplete="off" placeholder="发布你的评论" style={{width:754}}></Input ></Col>
<Col>
<Space>
<Button>取消</Button>
<Button type="primary">回复</Button>
</Space>
</Col>
</Row>
</div> */
}
</
Card
>
</
div
>
);
};
export
default
DiscussDetail
;
apps/admin/src/modules/books/sale/components/SaleDiscussionDetail.less
0 → 100644
浏览文件 @
77cf6ebc
.DiscussDetail {
.reply {
color: #aa1941;
}
.del {
color: #999999;
}
.card {
.ant-card-body {
height: calc(100vh - 260px);
overflow: auto;
}
}
.discussItem {
.discuss {
&::after {
display: block;
content: "";
width: 100%;
height: 1px;
background: #e5e5e57f;
margin-top: 16px;
margin-bottom: 10px;
}
.name {
font-size: 12px;
color: #333333;
font-weight: 500;
margin-bottom: 4px;
}
.content {
font-size: 12px;
color: #333333;
}
}
}
}
apps/admin/src/modules/books/sale/components/SaleEdit.jsx
浏览文件 @
77cf6ebc
export
{
default
}
from
"@/pages/books/sale/edit"
;
import
React
,
{
useEffect
,
useState
,
useRef
}
from
"react"
;
import
{
Form
,
Input
,
Select
,
Button
,
Radio
,
Space
,
Image
,
Spin
,
Tree
,
Drawer
,
Row
,
Col
,
message
,
}
from
"antd"
;
import
{
getInfoById
,
publisherList
,
publisherAdd
,
getListAll
,
editBook
}
from
"../api"
;
import
{
getClassifyList
}
from
"@/modules/books/classify/api"
;
import
{
useLocation
}
from
"react-router"
;
import
{
useNavigate
}
from
"react-router"
;
import
{
convertToAntdTreeData
}
from
"@/utils/common.js"
;
const
SaleEdit
=
()
=>
{
const
{
state
:
{
id
},
}
=
useLocation
();
const
[
initialValues
,
setinitialValues
]
=
useState
({
name
:
""
,
authors
:
""
,
category_id
:
null
,
producers_id
:
null
,
press_id
:
null
,
is_free
:
null
,
});
const
[
form
]
=
Form
.
useForm
();
const
navigate
=
useNavigate
();
const
[
showAddCategory
,
setShowAddCategory
]
=
useState
(
false
);
//弹出添加出品方
const
[
showAddCategory1
,
setShowAddCategory1
]
=
useState
(
false
);
const
[
showDrawer
,
setShowDrawer
]
=
useState
(
false
);
const
[
publisher_name
,
setPublisherName
]
=
useState
(
""
);
//出品方
const
[
publishers
,
setPublishers
]
=
useState
([]);
const
[
publisher_name1
,
setPublisherName1
]
=
useState
(
""
);
//出版社
const
[
publishers1
,
setPublishers1
]
=
useState
([]);
const
[
treeData
,
setTreeData
]
=
useState
([]);
// 在组件中添加一个状态来保存书籍类型的值
const
[
bookType
,
setBookType
]
=
useState
(
0
);
// 默认值为免费
const
[
producersConfig
,
setproducersConfig
]
=
useState
({
validateStatus
:
""
,
help
:
""
,
noStyle
:
false
,
style
:
{},
});
const
[
pressConfig
,
setpressConfig
]
=
useState
({
validateStatus
:
""
,
help
:
""
,
noStyle
:
false
,
style
:
{},
});
const
addproducers
=
async
(
obj
,
callbackFn
)
=>
{
const
{
types
}
=
obj
;
let
flag
=
true
;
if
(
types
==
1
)
{
if
(
!
publisher_name
)
{
flag
=
false
;
setproducersConfig
({
...
producersConfig
,
validateStatus
:
"error"
,
help
:
"请输入新的出品方"
,
noStyle
:
false
,
});
}
}
else
if
(
types
==
2
)
{
if
(
!
publisher_name1
)
{
flag
=
false
;
setpressConfig
({
...
pressConfig
,
validateStatus
:
"error"
,
help
:
"请输入新的出版社"
,
noStyle
:
false
,
});
}
}
if
(
!
flag
)
return
;
// 上面代码是校验
const
bool
=
await
publisherAdd
(
obj
);
if
(
!
bool
)
return
;
callbackFn
();
setproducersConfig
({
validateStatus
:
""
,
help
:
""
,
noStyle
:
false
,
style
:
{}
});
setpressConfig
({
validateStatus
:
""
,
help
:
""
,
noStyle
:
false
,
style
:
{}
});
getProducers
();
};
const
[
price
,
setprice
]
=
useState
(
0
);
const
[
vip_price
,
setvip_price
]
=
useState
(
0
);
const
[
discountVal
,
setDiscountVal
]
=
useState
(
0
);
const
calculateDiscountVal
=
(
price
,
vip_price
)
=>
{
if
(
Number
(
price
)
>
0
&&
Number
(
vip_price
)
>
0
)
{
return
(
Number
(
vip_price
)
/
Number
(
price
)).
toFixed
(
2
)
*
10
;
}
else
{
return
0
;
}
};
// 只在零售价改变时更新折扣率
useEffect
(()
=>
{
let
price
=
form
.
getFieldValue
(
"price"
);
let
vip_price
=
form
.
getFieldValue
(
"vip_price"
);
setDiscountVal
(
calculateDiscountVal
(
price
,
vip_price
).
toFixed
(
2
));
console
.
log
(
"discountVal"
,
discountVal
);
},
[
form
.
getFieldValue
(
"price"
),
form
.
getFieldValue
(
"vip_price"
)]);
// // 问题:输入3.3折后,输入框变成3.30000 ; 原因:toFixed()数字转字符串时可能会导致精度问题 ; 解决:parseFloat来解析浮点数
// const handleDiscountRateChange = (ev) => {
// const newVal = ev.target.value;
// if (Number(newVal) >= 0 && Number(newVal) <= 10) {
// setDiscountVal(newVal);
// const price = form.getFieldValue('price');
// updateVipPriceFromDiscountRate(price, newVal);
// }
// };
// const updateVipPriceFromDiscountRate = (price, discountRate) => {
// if (Number(price) > 0 && Number(discountRate) >= 0 && Number(discountRate) <= 10) {
// const vip_price = price * (discountRate / 10);
// form.setFieldsValue({ vip_price: vip_price.toFixed(2) });
// }
// };
// 在onChange中用parseFloat来解析浮点数,输入折扣数后,就触发折扣价改变,发现还是不太行,因为浮点数的精度问题可能会导致在 onChange 事件中出现意外行为。目前会更报错出现NaN,当在用户输入小数点和小数部分时,浮点数可能会被不完整地解析,导致计算错误,出现NaN
// const handleDiscountRateChange = (ev) => {
// const newVal = ev.target.value;
// if (Number(newVal) >= 0 && Number(newVal) <= 10) {
// const roundedVal = parseFloat(newVal).toFixed(2); // 对输入的值进行四舍五入处理
// setDiscountVal(roundedVal);
// const price = form.getFieldValue('price');
// // 添加计算并更新折扣金额的逻辑
// if (Number(price) > 0) {
// const vip_price = price * (roundedVal / 10);
// form.setFieldsValue({ vip_price: vip_price.toFixed(2) });
// }
// }
// };
// 将parseFloat放在失去焦点(onBlur)中处理,更靠谱
const
handleDiscountRateChange
=
(
ev
)
=>
{
const
newVal
=
ev
.
target
.
value
;
if
(
Number
(
newVal
)
>=
0
&&
Number
(
newVal
)
<=
10
&&
/^
\d
*
\.?\d{0,3}
$/
.
test
(
newVal
))
{
// 不在这里进行四舍五入处理,而是在失去焦点时处理
setDiscountVal
(
newVal
);
}
else
{
message
.
error
(
"最多输入小数点后3位,且不能大于10折"
);
newVal
.
slice
(
0
,
-
1
);
}
};
const
handleDiscountRateBlur
=
()
=>
{
const
newVal
=
parseFloat
(
discountVal
);
if
(
Number
.
isFinite
(
newVal
)
&&
newVal
>=
0
&&
newVal
<=
10
)
{
const
roundedVal
=
newVal
.
toFixed
(
3
);
setDiscountVal
(
roundedVal
);
const
price
=
form
.
getFieldValue
(
"price"
);
// 添加计算并更新折扣元的逻辑
if
(
Number
(
price
)
>
0
)
{
const
vip_price
=
price
*
(
roundedVal
/
10
);
form
.
setFieldsValue
({
vip_price
:
vip_price
.
toFixed
(
2
)
});
}
}
};
const
handleVippriceChange
=
(
ev
)
=>
{
const
newVal
=
ev
.
target
.
value
;
let
price
=
form
.
getFieldValue
(
"price"
);
if
(
Number
(
newVal
)
<=
price
&&
/^
\d
*
\.?\d{0,3}
$/
.
test
(
newVal
))
{
setvip_price
(
newVal
);
}
else
{
message
.
error
(
"最多输入小数点后3位,且不能大于零售价"
);
newVal
.
slice
(
0
,
-
1
);
}
};
useEffect
(()
=>
{
if
(
!
showAddCategory
)
{
setPublisherName
(
""
);
}
if
(
!
showAddCategory1
)
{
setPublisherName1
(
""
);
}
},
[
showAddCategory
,
showAddCategory1
]);
const
handleOpenDrawer
=
async
()
=>
{
setShowDrawer
(
true
);
const
data
=
await
getListAll
({
book_id
:
id
});
let
arr
=
convertToAntdTreeData
(
data
,
"name"
);
setTreeData
(
arr
);
};
const
handleCloseDrawer
=
()
=>
{
setShowDrawer
(
false
);
};
const
[
classData
,
setClassData
]
=
useState
([]);
const
[
producersData
,
setproducersData
]
=
useState
([]);
const
[
pressData
,
setpressData
]
=
useState
([]);
// 出品方/出版社
const
getProducers
=
async
()
=>
{
const
producersList
=
await
publisherList
({
types
:
1
});
const
press
=
await
publisherList
({
types
:
2
});
setproducersData
(
producersList
);
setpressData
(
press
);
};
const
[
FormLoad
,
setFormLoad
]
=
useState
(
true
);
const
[
checkedKeys
,
setcheckedKeys
]
=
useState
([]);
const
submitTree
=
(
obj
)
=>
{
form
.
setFieldValue
(
"trials"
,
checkedKeys
);
handleCloseDrawer
(
false
);
};
const
ontreeCheck
=
(
keys
,
{
checked
,
checkedNodes
,
node
,
halfCheckedKeys
})
=>
{
setcheckedKeys
(
keys
);
/* const handleCheck = (parentNode) => {
const keys = [];
const traverse = (currentNode) => {
keys.push(currentNode.key);
if (currentNode.children) {
currentNode.children.forEach((childNode) => {
traverse(childNode, currentNode.key);
});
}
};
traverse(parentNode);
return keys;
};
if (checked) {
const keysToCheck = handleCheck(node);
setcheckedKeys([...checkedKeys.checked, ...keysToCheck]);
} else {
const keysToUncheck = handleCheck(node);
const newCheckedKeys = checkedKeys.checked.filter((key) => !keysToUncheck.includes(key));
setcheckedKeys(newCheckedKeys);
} */
};
const
getinfo
=
async
()
=>
{
setFormLoad
(
true
);
// 详情
const
data
=
await
getInfoById
({
id
});
const
{
category_id
,
producers_id
,
press_id
}
=
data
;
let
obj
=
{
category_id
,
producers_id
,
press_id
};
setBookType
(
data
.
is_free
);
for
(
let
key
in
obj
)
{
if
(
obj
[
key
]
===
0
)
{
obj
[
key
]
=
null
;
}
}
form
.
setFieldsValue
({
...
data
,
...
obj
});
setFormLoad
(
false
);
};
const
getInfo
=
async
()
=>
{
// 分类
const
{
list
}
=
await
getClassifyList
({
page
:
1
,
page_size
:
999
});
setClassData
(
list
);
getProducers
();
getinfo
();
};
const
onFinish
=
async
(
obj
)
=>
{
const
bool
=
await
editBook
({
...
obj
,
id
});
if
(
!
bool
)
return
;
navigate
(
-
1
);
};
useEffect
(()
=>
{
getInfo
();
},
[]);
return
(
<
Spin
spinning=
{
FormLoad
}
style=
{
{
padding
:
20
}
}
>
<
Form
labelCol=
{
{
span
:
2
}
}
form=
{
form
}
initialValues=
{
initialValues
}
onFinish=
{
onFinish
}
>
<
Form
.
Item
label=
"书籍名称"
name=
"name"
>
<
Input
disabled
allowClear
autoComplete=
"off"
placeholder=
"请输入书籍名称"
style=
{
{
width
:
400
}
}
></
Input
>
</
Form
.
Item
>
<
Form
.
Item
label=
"书籍作者"
name=
"authors"
>
<
Input
autoComplete=
"off"
disabled
style=
{
{
width
:
260
}
}
></
Input
>
</
Form
.
Item
>
<
Form
.
Item
label=
"分类"
name=
"category_id"
rules=
{
[{
required
:
true
,
message
:
"请选择分类"
}]
}
>
<
Select
placeholder=
"请选择分类"
style=
{
{
width
:
260
}
}
>
{
classData
&&
classData
.
map
((
item
)
=>
(
<
Select
.
Option
value=
{
item
.
id
}
key=
{
item
.
id
}
>
{
item
.
name
}
</
Select
.
Option
>
))
}
</
Select
>
</
Form
.
Item
>
<
Form
.
Item
label=
"出品方"
required
style=
{
producersConfig
.
style
}
>
<
Space
>
<
Form
.
Item
noStyle
name=
"producers_id"
rules=
{
[{
required
:
true
,
message
:
"请选择出品方"
}]
}
>
<
Select
placeholder=
"请选择出品方"
style=
{
{
width
:
260
}
}
>
{
producersData
?.
map
((
item
)
=>
(
<
Select
.
Option
key=
{
item
.
id
}
value=
{
item
.
id
}
>
{
item
.
publisher_name
}
</
Select
.
Option
>
))
}
</
Select
>
</
Form
.
Item
>
<
a
style=
{
{
textDecoration
:
"underline"
,
color
:
"#1672EC"
,
cursor
:
"pointer"
,
}
}
onClick=
{
()
=>
{
setproducersConfig
({
...
producersConfig
,
style
:
{
marginBottom
:
0
}
});
setShowAddCategory
(
true
);
}
}
>
添加出品方
</
a
>
</
Space
>
{
showAddCategory
&&
(
<
div
style=
{
{
marginTop
:
"10px"
}
}
>
<
Space
>
<
Form
.
Item
noStyle=
{
producersConfig
.
noStyle
}
validateStatus=
{
producersConfig
.
validateStatus
}
help=
{
producersConfig
.
help
}
>
<
Input
style=
{
{
width
:
260
}
}
autoComplete=
"off"
placeholder=
"请输入新的出品方"
value=
{
publisher_name
}
allowClear
onChange=
{
(
e
)
=>
{
let
val
=
e
.
target
.
value
;
if
(
val
)
{
setproducersConfig
({
...
producersConfig
,
help
:
""
,
validateStatus
:
""
});
}
setPublisherName
(
val
);
}
}
/>
</
Form
.
Item
>
<
a
type=
"link"
style=
{
{
textDecoration
:
"underline"
,
color
:
"#1672EC"
}
}
onClick=
{
()
=>
addproducers
({
types
:
1
,
publisher_name
},
()
=>
setShowAddCategory
(
false
))
}
>
添加
</
a
>
<
a
type=
"link"
style=
{
{
textDecoration
:
"underline"
,
color
:
"#AA1941"
}
}
onClick=
{
()
=>
{
setproducersConfig
({
validateStatus
:
""
,
help
:
""
,
noStyle
:
false
,
style
:
{}
});
setShowAddCategory
(
false
);
// 可选:添加后关闭输入框
}
}
>
删除
</
a
>
</
Space
>
</
div
>
)
}
</
Form
.
Item
>
<
Form
.
Item
label=
"出版社"
required
style=
{
pressConfig
.
style
}
>
<
Space
>
<
Form
.
Item
noStyle
name=
"press_id"
rules=
{
[{
required
:
true
,
message
:
"请选择出版社"
}]
}
>
<
Select
placeholder=
"请选择出品方"
style=
{
{
width
:
260
}
}
>
{
pressData
?.
map
((
item
)
=>
{
return
(
<
Select
.
Option
key=
{
item
.
id
}
value=
{
item
.
id
}
>
{
item
.
publisher_name
}
</
Select
.
Option
>
);
})
}
</
Select
>
</
Form
.
Item
>
<
a
style=
{
{
textDecoration
:
"underline"
,
color
:
"#1672EC"
,
cursor
:
"pointer"
,
}
}
onClick=
{
()
=>
{
setpressConfig
({
...
pressConfig
,
style
:
{
marginBottom
:
0
}
});
setShowAddCategory1
(
true
);
}
}
>
添加出版社
</
a
>
</
Space
>
{
showAddCategory1
&&
(
<
div
style=
{
{
marginTop
:
"10px"
}
}
>
<
Space
>
<
Form
.
Item
noStyle=
{
pressConfig
.
noStyle
}
validateStatus=
{
pressConfig
.
validateStatus
}
help=
{
pressConfig
.
help
}
>
<
Input
style=
{
{
width
:
260
}
}
autoComplete=
"off"
placeholder=
"请输入新的出版社"
allowClear
value=
{
publisher_name1
}
onChange=
{
(
e
)
=>
{
let
val
=
e
.
target
.
value
;
if
(
val
)
{
setpressConfig
({
...
pressConfig
,
help
:
""
,
validateStatus
:
""
});
}
setPublisherName1
(
e
.
target
.
value
);
}
}
/>
</
Form
.
Item
>
<
a
type=
"link"
style=
{
{
textDecoration
:
"underline"
,
color
:
"#1672EC"
}
}
onClick=
{
()
=>
addproducers
({
types
:
2
,
publisher_name
:
publisher_name1
},
()
=>
setShowAddCategory1
(
false
),
)
}
>
添加
</
a
>
<
a
type=
"link"
style=
{
{
textDecoration
:
"underline"
,
color
:
"#AA1941"
}
}
onClick=
{
()
=>
{
setpressConfig
({
validateStatus
:
""
,
help
:
""
,
noStyle
:
false
,
style
:
{}
});
setShowAddCategory1
(
false
);
}
}
>
删除
</
a
>
</
Space
>
</
div
>
)
}
</
Form
.
Item
>
<
Form
.
Item
label=
"书籍类型"
name=
"is_free"
>
<
Radio
.
Group
onChange=
{
(
e
)
=>
{
setBookType
(
e
.
target
.
value
);
}
}
>
<
Radio
value=
{
1
}
>
免费
</
Radio
>
<
Radio
value=
{
0
}
>
付费
</
Radio
>
</
Radio
.
Group
>
</
Form
.
Item
>
{
bookType
===
0
&&
(
<
Form
.
Item
label=
"试读章节"
name=
"trials"
>
<
a
type=
"link"
style=
{
{
textDecoration
:
"underline"
}
}
onClick=
{
handleOpenDrawer
}
>
选择试读章节
</
a
>
</
Form
.
Item
>
)
}
{
bookType
===
0
&&
(
<>
<
Form
.
Item
label=
"零售价(元):"
name=
"price"
>
<
Input
value=
{
price
}
onChange=
{
(
e
)
=>
setprice
(
e
.
target
.
value
)
}
autoComplete=
"off"
allowClear
style=
{
{
width
:
80
}
}
></
Input
>
</
Form
.
Item
>
<
Form
.
Item
label=
"折扣价(元):"
>
<
Form
.
Item
name=
"vip_price"
style=
{
{
display
:
"inline-block"
}
}
>
<
Input
value=
{
vip_price
}
// onChange=
{(
e
)
=
>
setvip_price(e.target.value)}
onChange=
{
handleVippriceChange
}
autoComplete="off"
allowClear
style=
{
{
width
:
80
}
}
>
</
Input
>
</
Form
.
Item
>
<
Form
.
Item
style=
{
{
display
:
"inline-block"
,
margin
:
"0 8px"
}
}
>
<
Input
// value=
{
Number
(
discountVal
)
.
toFixed
(2)}
value=
{
discountVal
}
allowClear
onChange=
{
handleDiscountRateChange
}
onBlur=
{
handleDiscountRateBlur
}
// 添加失去焦点事件处理函数
autoComplete=
"off"
style=
{
{
width
:
80
}
}
></
Input
>
折
</
Form
.
Item
>
</
Form
.
Item
>
</>
)
}
<
Form
.
Item
wrapperCol=
{
{
offset
:
2
}
}
>
<
Space
className=
"footer"
size=
{
20
}
>
<
Button
onClick=
{
()
=>
{
navigate
(
-
1
);
}
}
>
取消
</
Button
>
<
Button
type=
"primary"
htmlType=
"submit"
>
确认
</
Button
>
</
Space
>
</
Form
.
Item
>
</
Form
>
<
Drawer
placement=
"right"
onClose=
{
handleCloseDrawer
}
open=
{
showDrawer
}
labelCol=
{
{
span
:
7
}
}
mask=
{
false
}
>
<
Form
onFinish=
{
submitTree
}
>
<
Form
.
Item
name=
"power_list"
style=
{
{
padding
:
10
}
}
>
<
Tree
checkable
checkedKeys=
{
checkedKeys
}
defaultExpandAll=
{
false
}
//让授权后的弹窗只展示根标签
treeData=
{
treeData
}
// showLine //删除这里,树形结构左侧的下拉线消失,图标从+-更改为默认的△
// checkStrictly
onCheck=
{
ontreeCheck
}
/>
</
Form
.
Item
>
<
Form
.
Item
wrapperCol=
{
{
offset
:
10
,
span
:
16
}
}
>
<
Space
size=
{
20
}
>
<
Button
onClick=
{
()
=>
handleCloseDrawer
(
false
)
}
>
取消
</
Button
>
<
Button
type=
"primary"
htmlType=
"submit"
>
提交
</
Button
>
</
Space
>
</
Form
.
Item
>
</
Form
>
</
Drawer
>
</
Spin
>
);
};
export
default
SaleEdit
;
apps/admin/src/modules/members/Layout.jsx
0 → 100644
浏览文件 @
77cf6ebc
import
{
Outlet
}
from
'react-router'
const
MembersLayout
=
()
=>
<
Outlet
/>
export
default
MembersLayout
apps/admin/src/modules/members/routes.tsx
浏览文件 @
77cf6ebc
...
...
@@ -2,7 +2,7 @@ import { lazy } from "react";
import
{
Navigate
,
useRoutes
}
from
"react-router"
;
import
ModuleBoundary
from
"@/app/ModuleBoundary"
;
const
MembersLayout
=
lazy
(()
=>
import
(
"
@/pages/member/index
"
));
const
MembersLayout
=
lazy
(()
=>
import
(
"
./Layout
"
));
const
MemberListRoutes
=
lazy
(()
=>
import
(
"./list/routes"
));
const
MemberLevelRoutes
=
lazy
(()
=>
import
(
"./level/routes"
));
...
...
apps/admin/src/modules/settings/SettingsLayout.jsx
0 → 100644
浏览文件 @
77cf6ebc
import
React
from
"react"
;
import
{
Outlet
}
from
"react-router"
;
const
Role
=
()
=>
{
return
(
<
div
>
<
Outlet
/>
</
div
>
);
};
export
default
Role
;
apps/admin/src/modules/settings/app-version/views/AppVersionListView.jsx
浏览文件 @
77cf6ebc
...
...
@@ -21,7 +21,7 @@ import dayjs from "dayjs";
import
Add
from
"@/assets/images/icon/add.png"
;
import
{
useSelector
}
from
"react-redux"
;
import
AliOSS
from
"ali-oss"
;
import
{
getAliOSSSTSToken
}
from
"
@/pages/setting/help/addedit/reque
t"
;
import
{
getAliOSSSTSToken
}
from
"
../../help/reques
t"
;
const
AppVersion
=
()
=>
{
const
[
data
,
setData
]
=
useState
([]);
const
[
page_size
,
setpage_size
]
=
useState
(
10
);
...
...
apps/admin/src/modules/settings/help/request.js
0 → 100644
浏览文件 @
77cf6ebc
import
axios
from
"@/utils/request.js"
;
import
qs
from
"qs"
;
import
{
message
}
from
"antd"
;
import
{
get
}
from
"lodash-es"
;
// oss
const
getAliOSSSTSToken
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/common/OssUpload/getUploadToken"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"获取OSS令牌失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
export
{
getAliOSSSTSToken
};
apps/admin/src/modules/settings/help/views/HelpAddEditView.jsx
浏览文件 @
77cf6ebc
...
...
@@ -11,7 +11,7 @@ import { uploadFiles } from "@/utils/upload";
import
AliOSS
from
"ali-oss"
;
import
dayjs
from
"dayjs"
;
import
{
getAliOSSSTSToken
}
from
"
@/pages/setting/help/addedit/reque
t"
;
import
{
getAliOSSSTSToken
}
from
"
../reques
t"
;
import
"./HelpAddEditView.less"
;
...
...
apps/admin/src/modules/settings/routes.tsx
浏览文件 @
77cf6ebc
...
...
@@ -2,7 +2,7 @@ import { lazy } from "react";
import
{
Navigate
,
useRoutes
}
from
"react-router"
;
import
ModuleBoundary
from
"@/app/ModuleBoundary"
;
const
SettingsLayout
=
lazy
(()
=>
import
(
"
@/pages/setting/index
"
));
const
SettingsLayout
=
lazy
(()
=>
import
(
"
./SettingsLayout
"
));
const
HelpRoutes
=
lazy
(()
=>
import
(
"./help/routes"
));
const
AppliedRoutes
=
lazy
(()
=>
import
(
"./applied/routes"
));
const
PersonalizedRoutes
=
lazy
(()
=>
import
(
"./personalized/routes"
));
...
...
apps/admin/src/modules/student-book/api.ts
浏览文件 @
77cf6ebc
export
*
from
"
@/pages/student/book
/request"
;
export
*
from
"
.
/request"
;
apps/admin/src/modules/student-book/request.js
0 → 100644
浏览文件 @
77cf6ebc
import
axios
from
"@/utils/request.js"
;
import
qs
from
"qs"
;
import
{
get
}
from
"lodash-es"
;
import
{
message
}
from
"antd"
;
// 用户信息
const
getUserInfo
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/user/user/getUserInfo"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 获取角色已选权限
const
getPowerByRoleId
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/user/role/getPowerByRoleId"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 章节列表
const
getAllList
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/chapter/getAllList"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
{
code
,
data
,
};
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 添加章节
const
chapterAdd
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/chapter/add"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
// message.success('添加成功');
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 章节详情
const
getInfoById
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/chapter/getInfoById"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 修改章节
const
chapterEdit
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/chapter/edit"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
message
.
success
(
msg
);
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 删除章节
const
chapterDel
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/chapter/del"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
message
.
success
(
msg
);
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 拖拽章节排序
const
dragOrder
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/chapter/dragOrder"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
message
.
success
(
msg
);
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 获取章节内容
const
getInfoByChapterId
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/section/getInfoByChapterId"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 添加章节内容
const
sectionAdd
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/section/add"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
message
.
success
(
msg
);
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 修改章节内容
const
sectionEdit
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/section/edit"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
// message.success(msg);
return
true
;
}
else
{
message
.
error
(
msg
);
return
{
code
,
msg
,
data
};
}
}
catch
(
error
)
{
return
false
;
}
};
// 获取章节的题库列表
const
getQuestionList
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/chapter/question/getQuestionList"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 移除课后题
const
questionDel
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/chapter/question/del"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 添加课后题
const
questionAdd
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/chapter/question/add"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 发布审核
const
release
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/release"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 模板列表
const
templateGetList
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/template/getList"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 模板导入
const
templateImport
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/template/import"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 书籍导入
const
teacherImport
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/import"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 保存历史记录
const
getRecordList
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/section/getRecordList"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
export
{
getAllList
,
chapterAdd
,
getInfoById
,
chapterEdit
,
chapterDel
,
dragOrder
,
getInfoByChapterId
,
sectionAdd
,
sectionEdit
,
getQuestionList
,
questionDel
,
questionAdd
,
release
,
templateGetList
,
templateImport
,
teacherImport
,
getRecordList
,
getPowerByRoleId
,
getUserInfo
,
};
apps/admin/src/modules/student-book/views/StudentBookView.jsx
浏览文件 @
77cf6ebc
export
{
default
}
from
"@/pages/student/book"
;
import
{
useState
,
useEffect
,
useRef
}
from
"react"
;
import
{
Button
,
Row
,
Col
,
Descriptions
,
Tree
,
Dropdown
,
Spin
,
App
}
from
"antd"
;
import
{
EllipsisOutlined
,
DiffOutlined
,
MenuFoldOutlined
,
MenuUnfoldOutlined
,
}
from
"@ant-design/icons"
;
import
WangEditorCustomer
from
"@/common/wangeditor-customer"
;
import
{
useLocation
,
useNavigate
,
useSearchParams
}
from
"react-router"
;
import
{
useDispatch
,
useSelector
}
from
"react-redux"
;
import
{
setAutosaveTime
}
from
"@/store/modules/editor"
;
import
{
setTreeChapter
}
from
"@/store/modules/user"
;
import
{
get
}
from
"lodash-es"
;
import
md5
from
"js-md5"
;
import
{
convertToAntdTreeData
,
findTreeElementByKey
,
findFirstNotHasChildren
,
findParentLevelOne
,
findTreeToIndex
,
findNodeById
,
}
from
"@/utils/common"
;
import
{
getAllList
,
getInfoByChapterId
,
sectionEdit
,
chapterDel
,
dragOrder
}
from
"../request"
;
import
"./StudentBookView.less"
;
const
Examine
=
()
=>
{
const
{
modal
}
=
App
.
useApp
();
const
[
searchParams
]
=
useSearchParams
();
const
location
=
useLocation
();
const
id
=
searchParams
.
get
(
"book_id"
);
const
isAdminOrCreator
=
get
(
location
,
"state.isAdminOrCreator"
,
false
);
const
{
treeChapter
,
userInfo
}
=
useSelector
((
state
)
=>
state
.
user
);
const
navigate
=
useNavigate
();
const
dispatch
=
useDispatch
();
const
[
gData
,
setGData
]
=
useState
([]);
const
[
chapterId
,
setChapterId
]
=
useState
(
0
);
const
[
bookId
,
setBookId
]
=
useState
(
0
);
// 树节点设置
const
[
expandedKeys
,
setExpandedKeys
]
=
useState
([]);
const
[
checkedKeys
,
setCheckedKeys
]
=
useState
([]);
const
[
nowTitle
,
setNowTitle
]
=
useState
(
""
);
// 编辑操作
const
[
isCollapse
,
setisCollapse
]
=
useState
(
false
);
const
[
editKey
,
setEditKey
]
=
useState
(
null
);
const
[
parentId
,
setParentId
]
=
useState
(
null
);
const
[
editValue
,
setEditValue
]
=
useState
(
""
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
contentMd5
,
setContentMd5
]
=
useState
(
""
);
// 设置编者
const
[
currentChapter
,
setCurrentChapter
]
=
useState
(
null
);
const
[
editChapterEditorsIsOpen
,
setEditChapterEditorsIsOpen
]
=
useState
(
false
);
const
onChapterEditorsChange
=
()
=>
{
setEditChapterEditorsIsOpen
(
false
);
};
// 编辑器内容
const
editorRef
=
useRef
();
const
saveInterRef
=
useRef
();
const
[
html
,
setHtml
]
=
useState
(
""
);
const
[
contentId
,
setContentId
]
=
useState
(
false
);
// 获取目录结构
const
getChapterTreeList
=
async
()
=>
{
setLoading
(
true
);
const
{
data
=
[],
code
}
=
await
getAllList
({
book_id
:
id
});
if
(
code
!==
200
)
{
setLoading
(
false
);
navigate
(
"/books/management/list"
,
{
replace
:
true
});
return
;
}
if
(
data
.
length
===
0
)
{
setLoading
(
false
);
return
;
}
const
uniqueBookList
=
[...
new
Set
(
data
.
map
((
item
)
=>
item
.
book_id
))][
0
];
setBookId
(
uniqueBookList
);
const
arr
=
convertToAntdTreeData
(
data
,
"name"
);
setGData
(
arr
);
const
allKeys
=
getAllNodeKeys
(
arr
);
setExpandedKeys
(
allKeys
);
const
first
=
findFirstNotHasChildren
(
arr
);
setNowTitle
(
first
.
title
);
if
(
treeChapter
&&
Object
.
entries
(
treeChapter
).
length
>
0
)
{
if
(
parseInt
(
treeChapter
.
saveBookId
)
===
parseInt
(
id
))
{
const
childInKey
=
findTreeElementByKey
(
arr
,
"key"
,
searchParams
.
get
(
"chapter_id"
)
||
treeChapter
.
saveChapterId
,
);
if
(
childInKey
)
{
setChapterId
(
childInKey
.
key
);
setCheckedKeys
([
childInKey
.
key
]);
setNowTitle
(
childInKey
.
title
);
}
else
{
setChapterId
(
first
.
key
);
setCheckedKeys
([
first
.
key
]);
setNowTitle
(
first
.
title
);
}
}
else
{
setChapterId
(
first
.
key
);
setCheckedKeys
([
first
.
key
]);
setNowTitle
(
first
.
title
);
await
dispatch
(
setTreeChapter
({
saveBookId
:
id
,
saveChapterId
:
first
.
key
}));
}
}
else
{
setChapterId
(
first
.
key
);
setCheckedKeys
([
first
.
key
]);
setNowTitle
(
first
.
title
);
}
setLoading
(
false
);
};
const
newChapterChange
=
(
id
,
title
)
=>
{
setChapterId
(
parseInt
(
id
));
setCheckedKeys
([
parseInt
(
id
)]);
setNowTitle
(
title
);
};
// 递归函数,获取所有节点的key
const
getAllNodeKeys
=
(
nodes
)
=>
{
let
keys
=
[];
nodes
.
forEach
((
node
)
=>
{
keys
.
push
(
node
.
key
);
if
(
node
.
children
&&
node
.
children
.
length
>
0
)
{
keys
=
keys
.
concat
(
getAllNodeKeys
(
node
.
children
));
}
});
return
keys
;
};
const
[
disabled
,
setDisabled
]
=
useState
(
false
);
// 获取内容
const
getChapterVal
=
async
()
=>
{
setLoading
(
true
);
const
data
=
await
getInfoByChapterId
({
chapter_id
:
chapterId
});
setDisabled
(
!
data
.
selected_editor_ids
.
includes
(
userInfo
.
id
)
&&
!
isAdminOrCreator
);
dispatch
(
setTreeChapter
({
saveBookId
:
id
,
saveChapterId
:
chapterId
,
...
data
}));
const
{
content
,
id
:
cId
}
=
data
;
if
(
content
)
{
setContentMd5
(
md5
(
content
));
}
setHtml
(
content
);
setContentId
(
cId
);
if
(
editorRef
.
current
&&
editorRef
.
current
.
editor
)
{
editorRef
.
current
.
editor
.
setHtml
(
content
);
}
setLoading
(
false
);
};
const
onExpand
=
(
expandedKeys
)
=>
{
setExpandedKeys
(
expandedKeys
);
};
const
handleSelect
=
async
(
checkedKeys
,
info
)
=>
{
const
{
key
}
=
info
.
node
;
const
childInKey
=
findTreeElementByKey
(
gData
,
"key"
,
key
);
if
(
childInKey
.
children
&&
childInKey
.
children
.
length
>
0
)
{
clearInterval
(
saveInterRef
.
current
);
// 进行展开操作
const
tempExpandeds
=
JSON
.
parse
(
JSON
.
stringify
(
expandedKeys
));
let
newExpand
=
""
;
if
(
expandedKeys
.
includes
(
key
))
{
newExpand
=
tempExpandeds
.
filter
((
item
)
=>
parseInt
(
item
)
!==
parseInt
(
key
));
}
else
{
newExpand
=
[...
tempExpandeds
,
key
];
}
setExpandedKeys
(
newExpand
);
setCheckedKeys
([
key
]);
return
;
}
else
{
const
{
key
,
title
}
=
info
.
node
;
if
(
info
.
selected
===
false
)
{
setLoading
(
true
);
setChapterId
(
key
);
setCheckedKeys
([
key
]);
setNowTitle
(
title
);
setLoading
(
false
);
}
else
{
setLoading
(
true
);
clearInterval
(
saveInterRef
.
current
);
await
saveContent
();
if
(
key
!==
chapterId
)
{
editorRef
.
current
.
editor
.
clear
();
}
setLoading
(
true
);
setChapterId
(
checkedKeys
[
0
]);
setCheckedKeys
([
checkedKeys
[
0
]]);
setNowTitle
(
info
.
node
.
title
);
setLoading
(
false
);
}
}
};
useEffect
(()
=>
{
getChapterTreeList
();
},
[]);
useEffect
(()
=>
{
if
(
chapterId
)
{
setCheckedKeys
([
chapterId
]);
getChapterVal
();
}
},
[
chapterId
]);
// 保存方法
const
saveContent
=
async
()
=>
{
return
;
if
((
contentId
,
editorRef
.
current
,
editorRef
.
current
.
editor
,
!
loading
))
{
const
contentHtml
=
editorRef
.
current
.
editor
.
getHtml
();
const
newMd5
=
md5
(
contentHtml
);
clearInterval
(
saveInterRef
.
current
);
const
word_count
=
editorRef
.
current
.
editor
.
getText
().
length
;
// 保存
const
data
=
await
sectionEdit
({
id
:
contentId
,
content
:
contentHtml
,
word_count
,
});
if
(
data
)
{
if
((
data
.
code
&&
data
.
code
===
3000
)
||
data
.
code
===
"3000"
)
{
navigate
(
"/books/management/list"
);
return
;
}
else
{
setContentMd5
(
newMd5
);
dispatch
(
setAutosaveTime
(
Date
.
now
()));
}
}
}
};
useEffect
(()
=>
{
if
(
!
loading
&&
contentId
)
{
clearInterval
(
saveInterRef
.
current
);
}
else
{
clearInterval
(
saveInterRef
.
current
);
}
},
[
contentId
,
loading
,
contentMd5
,
html
]);
const
addChapterParent
=
()
=>
{
setEditKey
(
-
1
);
setEditValue
(
""
);
setParentId
(
-
1
);
};
const
delChapter
=
async
(
node
)
=>
{
const
childInKey
=
findParentLevelOne
(
gData
,
"key"
,
node
.
key
);
let
current
=
null
;
if
(
childInKey
&&
childInKey
.
children
&&
childInKey
.
children
.
length
>
0
)
{
const
index
=
findTreeToIndex
(
childInKey
.
children
,
"key"
,
node
.
key
);
if
(
childInKey
.
children
.
length
===
1
)
{
current
=
childInKey
;
}
else
{
// 前面还有
if
(
index
<=
childInKey
.
children
.
length
)
{
if
(
index
===
0
)
{
current
=
childInKey
.
children
[
index
+
1
];
}
else
{
current
=
childInKey
.
children
[
index
-
1
];
}
}
}
}
else
{
const
index
=
findTreeToIndex
(
gData
,
"key"
,
node
.
key
);
let
child
=
null
;
if
(
index
===
0
)
{
child
=
gData
[
index
+
1
];
}
else
{
child
=
gData
[
index
-
1
];
}
if
(
child
&&
child
.
children
&&
child
.
children
.
length
>
0
)
{
current
=
child
.
children
[
0
];
}
else
{
current
=
child
;
}
}
const
data
=
await
chapterDel
({
id
:
node
.
key
});
if
(
data
)
{
await
dispatch
(
setTreeChapter
({
saveBookId
:
id
,
saveChapterId
:
current
?
current
.
key
:
null
}),
);
await
getChapterTreeList
();
}
};
useEffect
(()
=>
{
if
(
gData
&&
gData
.
length
>
0
&&
treeChapter
&&
Object
.
entries
(
treeChapter
).
length
>
0
)
{
if
(
parseInt
(
treeChapter
.
saveBookId
)
===
parseInt
(
id
))
{
const
childInKey
=
findTreeElementByKey
(
gData
,
"key"
,
treeChapter
.
saveChapterId
);
if
(
childInKey
&&
Object
.
entries
(
childInKey
).
length
>
0
)
{
setChapterId
(
parseInt
(
treeChapter
.
saveChapterId
));
setCheckedKeys
([
parseInt
(
treeChapter
.
saveChapterId
)]);
setNowTitle
(
childInKey
.
title
);
}
}
}
},
[
gData
,
treeChapter
]);
let
chapterMenuItems
=
[
{
key
:
"1"
,
label
:
"展开全部"
},
// { key: '2', label: '添加子节' },
// { key: '5', label: '设置编者' },
// { key: '3', label: '编辑' },
// { key: '4', label: '删除' }
];
// if (!isAdminOrCreator) {
// chapterMenuItems = [{ key: '1', label: '展开全部' }]
// }
const
handleMenuClick
=
async
(
e
,
node
)
=>
{
e
.
domEvent
.
stopPropagation
();
setCurrentChapter
(
node
);
if
(
parseInt
(
e
.
key
)
===
1
)
{
// 展开全部
const
ids
=
findNodeById
([
node
],
"key"
,
node
.
key
);
const
expandedKeysTemp
=
[...
expandedKeys
,
node
.
key
,
...
ids
];
setExpandedKeys
(
expandedKeysTemp
);
}
else
if
(
parseInt
(
e
.
key
)
===
2
)
{
// 添加子节
setEditKey
(
-
1
);
setEditValue
(
""
);
setParentId
(
node
.
key
);
}
else
if
(
parseInt
(
e
.
key
)
===
3
)
{
// 编辑
setEditKey
(
node
.
key
);
setEditValue
(
node
.
title
);
setParentId
(
false
);
}
else
if
(
parseInt
(
e
.
key
)
===
4
)
{
await
modal
.
confirm
({
title
:
"删除确认"
,
content
:
`确认删除子节【
${
node
.
title
}
】?`
,
onOk
()
{
delChapter
(
node
);
},
});
}
else
if
(
e
.
key
==
5
)
{
setEditChapterEditorsIsOpen
(
true
);
}
};
// 章节名称
const
chapterTitleRender
=
(
node
)
=>
{
return
(
<
div
className=
"chapter-tree-item"
>
<
p
className=
"title"
>
{
node
.
title
}
</
p
>
<
Dropdown
trigger=
{
[
"click"
]
}
menu=
{
{
items
:
chapterMenuItems
,
onClick
:
(
e
)
=>
handleMenuClick
(
e
,
node
)
}
}
>
<
EllipsisOutlined
onClick=
{
(
e
)
=>
e
.
stopPropagation
()
}
/>
</
Dropdown
>
</
div
>
);
};
const
onDrop
=
async
(
info
)
=>
{
const
dropToGap
=
info
.
dropToGap
;
// true 为同级或最大级 false 为子级
const
nowDropNode
=
info
.
dragNode
.
key
;
const
sideDropNode
=
info
.
node
.
key
;
let
data
=
null
;
if
(
dropToGap
)
{
// 同级
const
dragOverGapBottom
=
info
.
node
.
dragOverGapBottom
;
const
dragOver
=
info
.
node
.
dragOver
;
const
dragOverGapTop
=
info
.
node
.
dragOverGapTop
;
if
(
!
dragOverGapTop
&&
!
dragOver
&&
!
dragOverGapBottom
)
{
// 拖到最底部
// 拖拽到最顶层
data
=
await
dragOrder
({
book_id
:
id
,
drag_node
:
nowDropNode
,
position_node
:
sideDropNode
,
drop_type
:
"after"
,
});
}
else
{
if
(
dragOverGapTop
)
{
// 拖拽到最顶层
data
=
await
dragOrder
({
book_id
:
id
,
drag_node
:
nowDropNode
,
position_node
:
sideDropNode
,
drop_type
:
"before"
,
});
}
else
if
(
dragOverGapBottom
)
{
data
=
await
dragOrder
({
book_id
:
id
,
drag_node
:
nowDropNode
,
position_node
:
sideDropNode
,
drop_type
:
"after"
,
});
}
}
}
else
{
// 子级
data
=
await
dragOrder
({
book_id
:
id
,
drag_node
:
nowDropNode
,
position_node
:
sideDropNode
,
drop_type
:
"inner"
,
});
}
if
(
data
)
{
await
getChapterTreeList
();
}
};
return
(
<
div
className=
"examine"
style=
{
{
padding
:
"20px"
,
height
:
"100vh"
,
boxSizing
:
"border-box"
}
}
>
<
div
className=
"content-box"
>
<
Row
gutter=
{
10
}
style=
{
{
height
:
"100%"
}
}
className=
"book-content-row"
>
<
Col
span=
{
isCollapse
?
1
:
4
}
className=
"book-content-tree"
>
<
div
className=
"border"
>
{
!
isCollapse
?
(
<>
<
Descriptions
layout=
"vertical"
rootClassName=
"section-left-top"
bordered
items=
{
[
{
key
:
"1"
,
label
:
(
<
Row
gutter=
{
5
}
justify=
{
"space-between"
}
style=
{
{
alignItems
:
"center"
}
}
>
<
Col
>
章节目录
</
Col
>
<
Col
>
{
isAdminOrCreator
&&
(
<
Button
type=
"text"
icon=
{
<
DiffOutlined
/>
}
onClick=
{
addChapterParent
}
></
Button
>
)
}
<
Button
type=
"text"
icon=
{
<
MenuFoldOutlined
/>
}
onClick=
{
()
=>
setisCollapse
(
true
)
}
></
Button
>
</
Col
>
</
Row
>
),
},
]
}
/>
{
gData
&&
gData
.
length
>
0
&&
(
<
Tree
className=
"draggable-tree"
onSelect=
{
handleSelect
}
defaultExpandAll
defaultExpandedKeys=
{
expandedKeys
}
expandedKeys=
{
expandedKeys
}
defaultSelectedKeys=
{
checkedKeys
}
selectedKeys=
{
checkedKeys
}
blockNode
disabled=
{
loading
}
treeData=
{
gData
}
onExpand=
{
onExpand
}
titleRender=
{
(
nodeData
)
=>
chapterTitleRender
(
nodeData
)
}
/>
)
}
</>
)
:
(
<
Descriptions
layout=
"vertical"
bordered
items=
{
[
{
key
:
"1"
,
label
:
(
<
Row
gutter=
{
5
}
justify=
{
"space-between"
}
style=
{
{
alignItems
:
"center"
}
}
>
<
Col
>
<
Button
type=
"text"
icon=
{
<
MenuUnfoldOutlined
/>
}
onClick=
{
()
=>
setisCollapse
(
false
)
}
></
Button
>
</
Col
>
</
Row
>
),
},
]
}
/>
)
}
</
div
>
</
Col
>
<
Col
span=
{
isCollapse
?
23
:
20
}
className=
"book-content-tree"
>
<
div
className=
"editor-right"
>
<
Spin
spinning=
{
loading
}
>
<
WangEditorCustomer
ref=
{
editorRef
}
chapterId=
{
chapterId
}
bookId=
{
id
}
contentId=
{
contentId
}
setHtml=
{
setHtml
}
html=
{
html
}
saveContent=
{
saveContent
}
gData=
{
gData
}
nowTitle=
{
nowTitle
}
disabled=
{
disabled
}
isView=
{
true
}
/>
</
Spin
>
</
div
>
</
Col
>
</
Row
>
</
div
>
</
div
>
);
};
export
default
Examine
;
apps/admin/src/
pages/student/book/index
.less
→
apps/admin/src/
modules/student-book/views/StudentBookView
.less
浏览文件 @
77cf6ebc
File moved
apps/admin/src/modules/teachers/Layout.jsx
0 → 100644
浏览文件 @
77cf6ebc
import
{
Outlet
}
from
'react-router'
const
TeachersLayout
=
()
=>
<
Outlet
/>
export
default
TeachersLayout
apps/admin/src/modules/teachers/list/views/TeacherListView.jsx
浏览文件 @
77cf6ebc
...
...
@@ -18,7 +18,7 @@ import dayjs from 'dayjs'
import
{
useSelector
}
from
'react-redux'
import
{
AppList
}
from
'@/shared/components/AppList'
import
{
get
AllList
}
from
'@/pages/jurisdiction/role/request.js
'
import
{
get
TeacherRoles
}
from
'@/modules/access/role/api
'
import
add
from
'@/assets/images/icon/add.png'
import
{
...
...
@@ -65,7 +65,7 @@ export default function TeacherListView() {
const
reloadList
=
(
nextFilters
=
filters
)
=>
init
(
nextFilters
)
useEffect
(()
=>
{
get
AllList
({
type
:
2
}
).
then
((
result
)
=>
result
&&
setRoleList
(
result
))
get
TeacherRoles
(
).
then
((
result
)
=>
result
&&
setRoleList
(
result
))
},
[])
useEffect
(()
=>
{
...
...
apps/admin/src/modules/teachers/routes.tsx
浏览文件 @
77cf6ebc
...
...
@@ -2,7 +2,7 @@ import { lazy } from "react";
import
{
Navigate
,
useRoutes
}
from
"react-router"
;
import
ModuleBoundary
from
"@/app/ModuleBoundary"
;
const
TeachersLayout
=
lazy
(()
=>
import
(
"
@/pages/teacher/index
"
));
const
TeachersLayout
=
lazy
(()
=>
import
(
"
./Layout
"
));
const
TeacherListRoutes
=
lazy
(()
=>
import
(
"./list/routes"
));
export
default
function
TeachersRoutes
()
{
...
...
apps/admin/src/pages/advertisement/index.jsx
浏览文件 @
77cf6ebc
import
{
Outlet
}
from
"react-router"
;
const
AdvertisingModule
=
()
=>
{
return
<
Outlet
/>;
};
export
default
AdvertisingModule
;
export
{
default
}
from
'@/modules/advertisements/Layout'
apps/admin/src/pages/books/audit/index.less
浏览文件 @
77cf6ebc
.auditPanel {
background: #fbfbfb;
margin-bottom: 30px;
}
.auditdetail {
.ant-steps-item-icon {
width: 50px !important;
height: 100px;
}
.imgel {
.user {
width: 50px;
height: 50px;
position: relative;
background-size: cover;
}
.status {
width: 20px;
height: 20px;
position: absolute;
right: 0;
bottom: 0;
}
}
// .ant-steps-item-description {
// height: 150px;
// }
.ant-steps-item-tail {
display: none !important;
}
}
@import "@/modules/books/audit/audit.less";
apps/admin/src/pages/books/audit/preview.jsx
浏览文件 @
77cf6ebc
import
React
,
{
useEffect
,
useState
,
useRef
,
useImperativeHandle
,
forwardRef
}
from
"react"
;
import
{
Modal
,
Drawer
,
Tree
,
Spin
,
Space
,
Button
,
Input
,
Image
}
from
"antd"
;
import
{
CloseOutlined
,
LeftOutlined
,
BarsOutlined
}
from
"@ant-design/icons"
;
import
{
getInfoByChapterId
,
getAllList
}
from
"@/pages/books/section/request"
;
import
{
getChapterTopic
,
expandReadInfo
}
from
"@/common/wangeditor-customer/utils/request"
;
import
"@/common/wangeditor-customer/utils/iconfont"
;
import
{
convertToAntdTreeData
,
findTreeElementByKey
,
findFirstNotHasChildren
,
clacTopicText
,
}
from
"@/utils/common"
;
import
{
get
}
from
"lodash-es"
;
import
$
from
"jquery"
;
import
"@/common/preview.less"
;
import
hljs
from
"highlight.js"
;
import
"highlight.js/styles/github.css"
;
const
correctList
=
[
"A"
,
"B"
,
"C"
,
"D"
,
"E"
,
"F"
,
"G"
,
"H"
,
"I"
,
"J"
,
"K"
,
"L"
,
"M"
,
"N"
,
"O"
,
"P"
,
"Q"
,
"R"
,
"S"
,
"T"
,
"U"
,
"V"
,
"W"
,
"X"
,
"Y"
,
"Z"
,
];
// 递归遍历树,为叶子节点分配页码,返回 Map<key, pageNumber> 和总页数
const
buildPageMap
=
(
treeData
)
=>
{
const
map
=
{};
let
page
=
0
;
const
walk
=
(
nodes
)
=>
{
nodes
.
forEach
((
node
)
=>
{
if
(
node
.
children
&&
node
.
children
.
length
>
0
)
{
walk
(
node
.
children
);
}
else
{
page
+=
1
;
map
[
node
.
key
]
=
page
;
}
});
};
walk
(
treeData
);
return
{
map
,
total
:
page
};
};
const
PreviewScreen
=
(
props
,
ref
)
=>
{
const
{
bookId
}
=
props
;
const
[
gData
,
setGData
]
=
useState
([]);
const
[
loading
,
setLoading
]
=
useState
(
false
);
// 展示
const
[
prviewHtml
,
setPrviewHtml
]
=
useState
(
""
);
// 预览html
const
[
open
,
setOpen
]
=
useState
(
false
);
// 抽屉
const
[
secondType
,
setSecondType
]
=
useState
(
false
);
const
[
pageMap
,
setPageMap
]
=
useState
({});
// 叶子节点页码映射
const
[
totalPages
,
setTotalPages
]
=
useState
(
0
);
// 总页数
const
[
currentPage
,
setCurrentPage
]
=
useState
(
0
);
// 当前页码
const
[
toolTip
,
setTooltip
]
=
useState
({});
// 树节点设置
const
[
expandedKeys
,
setExpandedKeys
]
=
useState
([]);
const
[
checkedKeys
,
setCheckedKeys
]
=
useState
([]);
const
[
headTitle
,
setHeadTitle
]
=
useState
(
""
);
const
[
oldTitle
,
setOldTitle
]
=
useState
(
""
);
const
[
newChapterId
,
setNewChapterId
]
=
useState
(
""
);
const
[
opend
,
setOpend
]
=
useState
(
false
);
const
[
gallery
,
setGallery
]
=
useState
([]);
const
[
galleryIndex
,
setGalleryIndex
]
=
useState
(
0
);
const
[
isType
,
setIsType
]
=
useState
(
""
);
const
[
topicList
,
setTopicList
]
=
useState
([]);
// 题库
const
[
radioList
,
setRadioList
]
=
useState
([]);
// 单选
const
[
checkBoxList
,
setCheckBoxList
]
=
useState
([]);
// 多选
const
[
judgeList
,
setJudgeList
]
=
useState
([]);
// 判断
const
[
fillList
,
setFillList
]
=
useState
([]);
// 填空
const
[
textareaList
,
setTextareaList
]
=
useState
([]);
// 简答
const
[
expandContent
,
setExpandContent
]
=
useState
(
""
);
// 扩展阅读内容
const
[
previewImg
,
setPreviewImg
]
=
useState
(
""
);
// 扩展阅读内容
const
newId
=
useRef
(
null
);
// 递归函数,获取所有节点的key
const
getAllNodeKeys
=
(
nodes
)
=>
{
let
keys
=
[];
nodes
.
forEach
((
node
)
=>
{
keys
.
push
(
node
.
key
);
if
(
node
.
children
&&
node
.
children
.
length
>
0
)
{
keys
=
keys
.
concat
(
getAllNodeKeys
(
node
.
children
));
}
});
return
keys
;
};
const
getChapterTreeList
=
async
()
=>
{
setLoading
(
true
);
const
data
=
await
getAllList
({
book_id
:
bookId
});
if
(
!
data
.
data
.
length
)
{
setLoading
(
false
);
return
;
}
const
arr
=
convertToAntdTreeData
(
data
.
data
,
"name"
);
const
{
map
,
total
}
=
buildPageMap
(
arr
);
setPageMap
(
map
);
setTotalPages
(
total
);
setGData
(
arr
);
const
allKeys
=
getAllNodeKeys
(
arr
);
setExpandedKeys
(
allKeys
);
const
first
=
findFirstNotHasChildren
(
arr
);
setOldTitle
(
first
.
title
);
setNewChapterId
(
first
.
key
);
setCurrentPage
(
map
[
first
.
key
]
||
0
);
setLoading
(
false
);
};
const
getTopicList
=
async
({
practicenum
,
chapterid
,
bookid
})
=>
{
const
data
=
await
getChapterTopic
({
position
:
practicenum
,
book_id
:
bookid
,
chapter_id
:
chapterid
,
});
const
temp
=
[];
data
.
forEach
((
item
)
=>
{
let
obj
=
{
...
item
};
if
([
1
,
2
,
3
].
includes
(
parseInt
(
item
.
question_style
)))
{
try
{
obj
.
question_list
=
JSON
.
parse
(
item
.
question_list
);
}
catch
(
err
)
{
obj
.
question_list
=
[];
}
}
temp
.
push
(
obj
);
});
const
tempRadio
=
temp
.
filter
((
item
)
=>
parseInt
(
item
.
question_style
)
===
1
);
const
tempCheckbox
=
temp
.
filter
((
item
)
=>
parseInt
(
item
.
question_style
)
===
2
);
const
tempJudge
=
temp
.
filter
((
item
)
=>
parseInt
(
item
.
question_style
)
===
3
);
const
tempFill
=
temp
.
filter
((
item
)
=>
parseInt
(
item
.
question_style
)
===
4
);
const
tempTextarea
=
temp
.
filter
((
item
)
=>
parseInt
(
item
.
question_style
)
===
5
);
setRadioList
(
tempRadio
);
setCheckBoxList
(
tempCheckbox
);
setJudgeList
(
tempJudge
);
setFillList
(
tempFill
);
setTextareaList
(
tempTextarea
);
setTopicList
(
temp
);
};
const
getExpandInfo
=
async
({
chapterId
,
position
})
=>
{
const
data
=
await
expandReadInfo
({
book_id
:
bookId
,
chapter_id
:
chapterId
,
position
:
position
,
});
if
(
data
)
{
setExpandContent
(
data
.
content
);
}
};
const
getChapterId
=
async
()
=>
{
setLoading
(
true
);
newId
.
current
=
newChapterId
;
const
data
=
await
getInfoByChapterId
({
chapter_id
:
newChapterId
});
if
(
data
)
{
const
content
=
get
(
data
,
"content"
,
""
);
setPrviewHtml
(
content
);
$
(
"#previee-container"
)
.
find
(
"video audio"
)
.
each
((
index
,
item
)
=>
{
$
(
item
).
attr
(
"controls"
,
true
).
attr
(
"controlslist"
,
"nodownload"
);
});
setTimeout
(()
=>
{
document
.
querySelectorAll
(
".preview-content-show "
)[
0
]
.
querySelectorAll
(
"pre code"
)
.
forEach
((
el
)
=>
{
hljs
.
highlightElement
(
el
);
});
},
200
);
}
setLoading
(
false
);
};
const
handlerClick
=
()
=>
{
const
iParentW
=
$
(
".preview-content-it"
).
innerWidth
();
const
iParentH
=
$
(
".preview-content-it"
).
innerHeight
();
const
parentOffset
=
$
(
".preview-content-it"
).
offset
();
// 获取父元素相对于文档的位置
const
parentRect
=
$
(
".preview-content-it"
)[
0
].
getBoundingClientRect
();
// 获取父元素相对于文档的位置
$
(
".preview-content-html"
).
on
(
"click"
,
async
(
ev
)
=>
{
ev
.
stopPropagation
();
const
target
=
ev
.
target
;
const
tooltip
=
$
(
target
).
closest
(
".chapter-item-tooltip"
);
const
alink
=
$
(
target
).
closest
(
".chapter-item-link"
);
const
aGallery
=
$
(
target
).
closest
(
".chapter-gallery-container"
);
const
practice
=
$
(
target
).
closest
(
".chapter-practice"
);
const
aA
=
$
(
target
).
closest
(
"a"
);
const
aExpand
=
$
(
target
).
closest
(
".chapter-expand"
);
if
(
tooltip
.
length
>
0
)
{
const
random
=
$
(
tooltip
).
data
(
"random"
);
const
link
=
$
(
tooltip
).
data
(
"link"
);
const
content
=
$
(
tooltip
).
data
(
"content"
);
const
tooltipType
=
$
(
tooltip
).
data
(
"tooltip-type"
);
const
title
=
$
(
tooltip
).
data
(
"title"
);
const
tooltipParent
=
$
(
target
).
closest
(
"p, div, h1, h2, h3, h4, h5, h6, li, dt, dd, section"
,
);
const
tooltipParentPosition
=
$
(
tooltipParent
).
position
();
const
tooltipPosition
=
$
(
tooltip
).
position
();
const
childOffset
=
$
(
tooltip
).
offset
();
// 获取子元素相对于文档的位置
const
childWidth
=
$
(
tooltip
).
outerWidth
(
true
);
// 获取元素的宽度
const
childHeight
=
$
(
tooltip
).
outerHeight
(
true
);
// 获取元素的高度
let
toolRect
=
null
;
let
targetPositon
=
null
;
if
(
target
.
nodeName
.
toLowerCase
()
===
"svg"
||
target
.
nodeName
.
toLowerCase
()
===
"use"
)
{
toolRect
=
target
.
parentNode
.
parentNode
.
getBoundingClientRect
();
targetPositon
=
$
(
target
).
closest
(
".chapter-item-tooltip"
).
position
();
}
else
{
toolRect
=
target
.
parentNode
.
getBoundingClientRect
();
targetPositon
=
$
(
target
).
position
();
}
const
lineHeight
=
$
(
tooltip
).
css
(
"line-height"
);
let
inSide
=
""
;
let
left
=
childOffset
.
left
-
parentOffset
.
left
;
let
top
=
0
;
let
squareStyle
=
{};
if
(
childWidth
+
left
>=
iParentW
)
{
left
=
iParentW
-
childWidth
-
10
;
}
if
(
left
<=
0
)
{
left
=
10
;
}
let
style1
=
{
top
:
`
${
top
}
px`
,
left
:
`
${
left
}
px`
,
};
setTooltip
({
title
,
random
,
link
,
content
,
tooltipType
,
inSide
,
style
:
style1
,
squareStyle
:
squareStyle
,
});
setTimeout
(()
=>
{
const
childMoveHeight
=
$
(
".tooltip"
).
outerHeight
();
// 获取元素的高度
const
childMoveWidth
=
$
(
".tooltip"
).
outerWidth
();
// 获取元素的高度
const
scrollTop
=
$
(
".preview-content-show"
).
scrollTop
();
let
squareStyle
=
{};
if
(
left
<=
0
)
{
left
=
10
;
squareStyle
.
left
=
10
;
}
else
if
(
left
+
childMoveWidth
>=
iParentW
)
{
left
=
iParentW
-
childMoveWidth
-
20
;
squareStyle
.
left
=
targetPositon
.
left
-
left
;
}
else
{
squareStyle
.
left
=
childWidth
/
2
;
}
// 换行了
if
(
toolRect
.
height
/
1.5
>
parseInt
(
lineHeight
))
{
left
=
toolRect
.
right
-
parentRect
.
left
-
childMoveWidth
+
15
;
squareStyle
.
left
=
childMoveWidth
-
30
;
}
// 是否被遮挡
if
(
tooltipPosition
.
top
+
childMoveHeight
+
childHeight
+
30
>=
scrollTop
+
iParentH
)
{
inSide
=
"bottom"
;
top
=
tooltipPosition
.
top
-
childMoveHeight
;
squareStyle
.
left
=
childWidth
/
2
;
}
else
{
inSide
=
"top"
;
if
(
toolRect
.
height
/
1.5
>
parseInt
(
lineHeight
))
{
top
=
tooltipPosition
.
top
+
childHeight
;
}
else
{
top
=
tooltipPosition
.
top
+
childHeight
+
25
;
}
// squareStyle.left = childWidth / 2;
}
top
=
top
-
scrollTop
;
let
style2
=
{
top
:
`
${
top
}
px`
,
left
:
`
${
left
}
px`
,
zIndex
:
1000
,
opacity
:
1
,
};
setTooltip
({
title
,
random
,
link
,
content
,
tooltipType
,
inSide
,
style
:
style2
,
squareStyle
:
squareStyle
,
});
},
100
);
return
false
;
}
else
if
(
alink
.
length
>
0
)
{
const
link
=
$
(
alink
).
data
(
"link"
);
const
linkType
=
$
(
alink
).
data
(
"linktype"
);
const
chapters
=
$
(
alink
).
data
(
"chapters"
);
const
title
=
$
(
alink
).
data
(
"title"
);
let
last
=
""
;
if
(
parseInt
(
linkType
)
===
2
)
{
if
(
chapters
.
toString
().
indexOf
(
","
)
>
-
1
)
{
const
arr
=
chapters
.
split
(
","
);
last
=
arr
[
arr
.
length
-
1
];
}
else
{
last
=
chapters
;
}
setHeadTitle
(
title
);
setOpend
(
true
);
setNewChapterId
(
last
);
}
else
{
window
.
open
(
link
);
}
ev
.
preventDefault
();
return
false
;
}
else
if
(
aGallery
.
length
>
0
)
{
const
title
=
$
(
aGallery
).
data
(
"title"
);
const
flex
=
$
(
aGallery
).
data
(
"flex"
);
const
gallerylist
=
$
(
aGallery
).
data
(
"gallerylist"
);
const
galleryArr
=
JSON
.
parse
(
decodeURI
(
gallerylist
));
if
(
galleryArr
&&
galleryArr
.
length
>
0
&&
galleryArr
instanceof
Array
)
{
setGallery
(
galleryArr
);
if
(
parseInt
(
flex
)
===
2
)
{
setGalleryIndex
(
0
);
}
else
{
setGalleryIndex
(
$
(
target
).
closest
(
".chapter-gallery-item"
).
index
());
}
}
else
{
setGallery
([]);
}
setOpend
(
true
);
setHeadTitle
(
title
);
setIsType
(
"gallery"
);
setTooltip
({});
return
false
;
}
else
if
(
practice
.
length
>
0
)
{
const
title
=
$
(
practice
).
data
(
"title"
);
const
bookid
=
$
(
practice
).
data
(
"bookid"
);
const
chapterid
=
$
(
practice
).
data
(
"chapterid"
);
const
practicenum
=
$
(
practice
).
data
(
"practicenum"
);
getTopicList
({
practicenum
,
chapterid
,
bookid
});
setOpend
(
true
);
setHeadTitle
(
title
);
setIsType
(
"practice"
);
ev
.
preventDefault
();
return
false
;
}
else
if
(
aA
.
length
>
0
)
{
const
href
=
$
(
aA
).
attr
(
"href"
);
let
newLink
=
""
;
if
(
!
/^https*:
\/\/
/
.
test
(
href
))
{
newLink
=
`http://
${
href
}
`
;
}
else
{
newLink
=
href
;
}
window
.
open
(
newLink
);
ev
.
preventDefault
();
return
false
;
}
else
if
(
aExpand
.
length
>
0
)
{
const
title
=
$
(
aExpand
).
data
(
"title"
);
const
name
=
$
(
aExpand
).
data
(
"name"
);
const
position
=
$
(
aExpand
).
data
(
"random"
);
setOpend
(
true
);
setIsType
(
"expand"
);
setHeadTitle
(
name
);
await
getExpandInfo
({
chapterId
:
newId
.
current
,
position
});
}
else
if
(
target
.
nodeName
.
toLowerCase
()
===
"img"
)
{
const
alt
=
$
(
target
).
attr
(
"alt"
);
const
src
=
$
(
target
).
attr
(
"src"
);
setOpend
(
true
);
setIsType
(
"img"
);
setPreviewImg
(
src
);
setHeadTitle
(
alt
?
alt
:
"图片展示"
);
}
setTooltip
({});
setGallery
([]);
});
$
(
".expand-content, .gallery-prview-container"
).
on
(
"click"
,
(
ev
)
=>
{
const
target
=
ev
.
target
;
if
(
target
.
nodeName
.
toLowerCase
()
===
"img"
)
{
const
alt
=
$
(
target
).
attr
(
"alt"
);
const
src
=
$
(
target
).
attr
(
"src"
);
setOpend
(
true
);
setIsType
(
"img"
);
setPreviewImg
(
src
);
setSecondType
(
"扩展图片"
);
}
});
};
useEffect
(()
=>
{
handlerClick
();
},
[
isType
]);
useEffect
(()
=>
{
if
(
bookId
)
{
getChapterTreeList
();
}
},
[
bookId
]);
useEffect
(()
=>
{
if
(
newChapterId
)
{
getChapterId
();
}
},
[
newChapterId
]);
const
onClose
=
()
=>
{
setOpen
(
false
);
};
const
openDrawer
=
(
e
)
=>
{
e
.
preventDefault
();
e
.
stopPropagation
();
setOpen
(
true
);
};
const
handleSelect
=
async
(
checkedKeys
,
info
)
=>
{
if
(
info
.
node
.
children
&&
info
.
node
.
children
.
length
>
0
)
{
const
temp
=
JSON
.
parse
(
JSON
.
stringify
(
expandedKeys
));
if
(
temp
.
includes
(
info
.
node
.
key
))
{
const
newExpands
=
temp
.
filter
((
item
)
=>
item
!==
info
.
node
.
key
);
setExpandedKeys
([...
newExpands
]);
}
else
{
setExpandedKeys
([...
temp
,
info
.
node
.
key
]);
}
}
else
{
setOldTitle
(
info
.
node
.
title
);
setNewChapterId
(
info
.
node
.
key
);
newId
.
current
=
info
.
node
.
key
;
setCheckedKeys
(
checkedKeys
);
setCurrentPage
(
pageMap
[
info
.
node
.
key
]
||
0
);
setOpen
(
false
);
}
};
const
galleryLeft
=
()
=>
{
let
temp
=
galleryIndex
;
if
(
temp
<=
0
)
{
temp
=
0
;
}
else
{
temp
=
temp
-
1
;
}
setGalleryIndex
(
temp
);
};
const
galleryRight
=
()
=>
{
let
temp
=
galleryIndex
;
if
(
temp
>=
gallery
.
length
-
1
)
{
temp
=
gallery
.
length
-
1
;
}
else
{
temp
=
temp
+
1
;
}
setGalleryIndex
(
temp
);
};
return
(
<
div
className=
"priview-modal"
>
{
/* <Spin spinning={loading}> */
}
<
div
className=
"previee-container"
id=
"previee-container"
>
<
div
className=
"chapter-head-title"
>
<
h2
className=
"tree"
>
{
opend
?
(
<
Space
>
<
Button
type=
"text"
onClick=
{
()
=>
{
if
(
secondType
)
{
setSecondType
(
false
);
setPreviewImg
(
null
);
setIsType
(
"expand"
);
return
;
}
setOpend
(
false
);
setIsType
(
""
);
setExpandContent
(
""
);
setTopicList
([]);
setGalleryIndex
(
-
1
);
setNewChapterId
(
newChapterId
);
setPreviewImg
(
""
);
}
}
>
<
LeftOutlined
/>
</
Button
>
{
secondType
?
secondType
:
headTitle
}
</
Space
>
)
:
(
<>
<
span
className=
"no"
>
{
oldTitle
}
</
span
>
<
span
className=
"menu"
>
<
BarsOutlined
style=
{
{
fontSize
:
22
}
}
onClick=
{
()
=>
setOpen
(
true
)
}
/>
</
span
>
</>
)
}
</
h2
>
</
div
>
<
div
className=
"preview-content-it"
>
<
div
className=
"preview-content-show"
>
<
Spin
spinning=
{
loading
}
wrapperClassName=
"chapter-loading"
>
<
div
className=
"preview-content-html"
dangerouslySetInnerHTML=
{
{
__html
:
prviewHtml
}
}
></
div
>
</
Spin
>
{
currentPage
>
0
&&
(
<
div
className=
"preview-page-number"
>
<
span
>
{
currentPage
}
/
{
totalPages
}
</
span
>
</
div
>
)
}
</
div
>
{
isType
&&
(
<
div
className=
"preview-content-other"
>
{
isType
===
"gallery"
&&
(
<
div
className=
"gallery-prview-container"
>
{
gallery
&&
gallery
.
length
>
0
?
(
<>
<
div
className=
"gallery-img"
>
<
img
src=
{
gallery
[
parseInt
(
galleryIndex
)].
url
}
alt=
{
gallery
[
parseInt
(
galleryIndex
)].
title
}
/>
<
div
className=
"opa prev"
onClick=
{
galleryLeft
}
></
div
>
<
div
className=
"opa next"
onClick=
{
galleryRight
}
></
div
>
</
div
>
<
div
className=
"steps"
>
当前
{
parseInt
(
galleryIndex
)
+
1
}
/
{
gallery
.
length
}
张
</
div
>
<
div
className=
"title"
>
{
gallery
[
parseInt
(
galleryIndex
)].
title
}
</
div
>
<
div
className=
"desc"
>
{
gallery
[
parseInt
(
galleryIndex
)].
desc
}
</
div
>
</>
)
:
(
<>
<
div
className=
"gallery-img noData"
>
画廊数据异常
</
div
>
</>
)
}
</
div
>
)
}
{
isType
===
"practice"
&&
(
<
div
className=
"practice-insert-topic"
>
{
radioList
.
length
>
0
&&
(
<
div
className=
"topic_style"
>
<
h3
>
{
clacTopicText
(
"radio"
)
}
. 单选题
</
h3
>
<
div
className=
"practice-list"
>
{
radioList
.
map
((
item
,
index
)
=>
{
return
(
<
div
className=
"practice-item"
key=
{
index
}
>
<
div
className=
"topic-choose-title"
dangerouslySetInnerHTML=
{
{
__html
:
`<span class="index">${index + 1}.</span>${item.titles}`
,
}
}
></
div
>
<
div
className=
"topic_choose-list"
>
{
[
1
,
2
,
3
].
includes
(
parseInt
(
item
.
question_style
))
&&
item
.
question_list
.
map
((
citem
,
cindex
)
=>
{
return
(
<
div
className=
"topic_choose-item"
key=
{
cindex
}
>
<
div
className=
"choose-ico"
>
<
i
className=
{
`ico ${item.question_style === 2 ? "checkbox" : "radio"}`
}
></
i
>
</
div
>
<
div
className=
"topic_content"
dangerouslySetInnerHTML=
{
{
__html
:
`<span class="correct">${correctList[cindex]}.</span> ${citem.option}`
,
}
}
></
div
>
</
div
>
);
})
}
{
item
.
question_style
===
5
&&
(
<
div
>
<
Input
.
TextArea
disabled=
{
true
}
autoSize=
{
{
minRows
:
4
,
maxRows
:
6
}
}
/>
</
div
>
)
}
</
div
>
</
div
>
);
})
}
</
div
>
</
div
>
)
}
{
checkBoxList
.
length
>
0
&&
(
<
div
className=
"topic_style"
>
<
h3
>
{
clacTopicText
(
"checkbox"
,
radioList
)
}
. 多选题
</
h3
>
<
div
className=
"practice-list"
>
{
checkBoxList
.
map
((
item
,
index
)
=>
{
return
(
<
div
className=
"practice-item"
key=
{
index
}
>
<
div
className=
"topic-choose-title"
dangerouslySetInnerHTML=
{
{
__html
:
`<span class="index">${index + 1}.</span>${item.titles}`
,
}
}
></
div
>
<
div
className=
"topic_choose-list"
>
{
[
1
,
2
,
3
].
includes
(
parseInt
(
item
.
question_style
))
&&
item
.
question_list
.
map
((
citem
,
cindex
)
=>
{
return
(
<
div
className=
"topic_choose-item"
key=
{
cindex
}
>
<
div
className=
"choose-ico"
>
<
i
className=
{
`ico ${item.question_style === 2 ? "checkbox" : "radio"}`
}
></
i
>
</
div
>
<
div
className=
"topic_content"
dangerouslySetInnerHTML=
{
{
__html
:
`<span class="correct">${correctList[cindex]}.</span> ${citem.option}`
,
}
}
></
div
>
</
div
>
);
})
}
{
item
.
question_style
===
5
&&
(
<
div
>
<
Input
.
TextArea
disabled=
{
true
}
autoSize=
{
{
minRows
:
4
,
maxRows
:
6
}
}
/>
</
div
>
)
}
</
div
>
</
div
>
);
})
}
</
div
>
</
div
>
)
}
{
judgeList
.
length
>
0
&&
(
<
div
className=
"topic_style"
>
<
h3
>
{
clacTopicText
(
"judge"
,
radioList
,
checkBoxList
)
}
. 判断题
</
h3
>
<
div
className=
"practice-list"
>
{
judgeList
.
map
((
item
,
index
)
=>
{
return
(
<
div
className=
"practice-item"
key=
{
index
}
>
<
div
className=
"topic-choose-title"
dangerouslySetInnerHTML=
{
{
__html
:
`<span class="index">${index + 1}.</span>${item.titles}`
,
}
}
></
div
>
<
div
className=
"topic_choose-list"
>
{
[
1
,
2
,
3
].
includes
(
parseInt
(
item
.
question_style
))
&&
item
.
question_list
.
map
((
citem
,
cindex
)
=>
{
return
(
<
div
className=
"topic_choose-item"
key=
{
cindex
}
>
<
div
className=
"choose-ico"
>
<
i
className=
{
`ico ${item.question_style === 2 ? "checkbox" : "radio"}`
}
></
i
>
</
div
>
<
div
className=
"topic_content"
dangerouslySetInnerHTML=
{
{
__html
:
`<span class="correct">${correctList[cindex]}.</span> ${citem.option}`
,
}
}
></
div
>
</
div
>
);
})
}
{
item
.
question_style
===
5
&&
(
<
div
>
<
Input
.
TextArea
disabled=
{
true
}
autoSize=
{
{
minRows
:
4
,
maxRows
:
6
}
}
/>
</
div
>
)
}
</
div
>
</
div
>
);
})
}
</
div
>
</
div
>
)
}
{
fillList
.
length
>
0
&&
(
<
div
className=
"topic_style"
>
<
h3
>
{
clacTopicText
(
"fill"
,
radioList
,
checkBoxList
,
judgeList
)
}
. 填空题
</
h3
>
<
div
className=
"practice-list"
>
{
fillList
.
map
((
item
,
index
)
=>
{
return
(
<
div
className=
"practice-item"
key=
{
index
}
>
<
div
className=
"topic-choose-title"
dangerouslySetInnerHTML=
{
{
__html
:
`<span class="index">${index + 1}.</span>${item.titles}`
,
}
}
></
div
>
<
div
className=
"topic_choose-list"
>
{
[
1
,
2
,
3
].
includes
(
parseInt
(
item
.
question_style
))
&&
item
.
question_list
.
map
((
citem
,
cindex
)
=>
{
return
(
<
div
className=
"topic_choose-item"
key=
{
cindex
}
>
<
div
className=
"choose-ico"
>
<
i
className=
{
`ico ${item.question_style === 2 ? "checkbox" : "radio"}`
}
></
i
>
</
div
>
<
div
className=
"topic_content"
dangerouslySetInnerHTML=
{
{
__html
:
`<span class="correct">${correctList[cindex]}.</span> ${citem.option}`
,
}
}
></
div
>
</
div
>
);
})
}
{
item
.
question_style
===
5
&&
(
<
div
>
<
Input
.
TextArea
disabled=
{
true
}
autoSize=
{
{
minRows
:
4
,
maxRows
:
6
}
}
/>
</
div
>
)
}
</
div
>
</
div
>
);
})
}
</
div
>
</
div
>
)
}
{
textareaList
.
length
>
0
&&
(
<
div
className=
"topic_style"
>
<
h3
>
{
clacTopicText
(
"textarea"
,
radioList
,
checkBoxList
,
judgeList
,
fillList
)
}
.
简答题
</
h3
>
<
div
className=
"practice-list"
>
{
textareaList
.
map
((
item
,
index
)
=>
{
return
(
<
div
className=
"practice-item"
key=
{
index
}
>
<
div
className=
"topic-choose-title"
dangerouslySetInnerHTML=
{
{
__html
:
`<span class="index">${index + 1}.</span>${item.titles}`
,
}
}
></
div
>
<
div
className=
"topic_choose-list"
>
{
[
1
,
2
,
3
].
includes
(
parseInt
(
item
.
question_style
))
&&
item
.
question_list
.
map
((
citem
,
cindex
)
=>
{
return
(
<
div
className=
"topic_choose-item"
key=
{
cindex
}
>
<
div
className=
"choose-ico"
>
<
i
className=
{
`ico ${item.question_style === 2 ? "checkbox" : "radio"}`
}
></
i
>
</
div
>
<
div
className=
"topic_content"
dangerouslySetInnerHTML=
{
{
__html
:
`${correctList[cindex]}. ${citem.option}`
,
}
}
></
div
>
</
div
>
);
})
}
{
item
.
question_style
===
5
&&
(
<
div
>
<
Input
.
TextArea
disabled=
{
true
}
autoSize=
{
{
minRows
:
4
,
maxRows
:
6
}
}
/>
</
div
>
)
}
</
div
>
</
div
>
);
})
}
</
div
>
</
div
>
)
}
</
div
>
)
}
{
isType
===
"expand"
&&
(
<
div
className=
"expand-container"
>
<
div
className=
"expand-content"
dangerouslySetInnerHTML=
{
{
__html
:
expandContent
}
}
></
div
>
</
div
>
)
}
{
isType
===
"img"
&&
(
<
div
className=
"img-preview"
>
<
Image
width=
{
430
}
src=
{
previewImg
}
/>
</
div
>
)
}
</
div
>
)
}
<
div
className=
"tooltip"
style=
{
toolTip
.
style
}
>
<
div
className=
{
`square ${
toolTip.inSide === "top"
? "square_top"
: toolTip.inSide === "bottom"
? "square_bottom"
: ""
}`
}
style=
{
toolTip
.
squareStyle
}
></
div
>
<
div
className=
"tooltip-content-container"
>
<
div
className=
"tooltip-content"
>
<
div
className=
"content"
>
<
p
>
{
toolTip
.
content
}
</
p
>
</
div
>
{
toolTip
.
link
&&
(
<
div
className=
"content-opa"
>
<
a
href=
{
toolTip
.
link
}
target=
"_blank"
rel=
"noreferrer"
className=
"c-link"
></
a
>
{
/* <a
href={`https://baike.baidu.com/item/${toolTip?.title}`}
target='_blank'
rel='noreferrer'
>
<img src={baike} alt='baike' />
</a> */
}
</
div
>
)
}
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
<
Drawer
onClose=
{
onClose
}
open=
{
open
}
getContainer=
{
false
}
placement=
"left"
classNames=
{
{
header
:
"priview-drawer-header"
,
body
:
"priview-drawer-body"
}
}
width=
"300"
title=
"章节列表"
rootClassName=
"priview-drawer-container"
>
<
Tree
className=
"draggable-tree"
onSelect=
{
handleSelect
}
defaultExpandedKeys=
{
expandedKeys
}
expandedKeys=
{
expandedKeys
}
defaultExpandAll
blockNode
treeData=
{
gData
}
titleRender=
{
(
nodeData
)
=>
{
const
page
=
pageMap
[
nodeData
.
key
];
return
(
<
span
className=
"tree-node-title"
>
<
span
className=
"tree-node-name"
>
{
nodeData
.
title
}
</
span
>
{
page
!=
null
&&
<
span
className=
"tree-node-page"
>
P
{
page
}
</
span
>
}
</
span
>
);
}
}
/>
</
Drawer
>
{
/* </Spin> */
}
</
div
>
);
};
export
default
forwardRef
(
PreviewScreen
);
export
{
default
}
from
"@/modules/books/audit/components/PreviewModal"
;
apps/admin/src/pages/books/index.jsx
浏览文件 @
77cf6ebc
import
React
from
"react"
;
import
{
Outlet
}
from
"react-router"
;
const
Book
=
()
=>
{
return
<
Outlet
/>;
};
export
default
Book
;
export
{
default
}
from
"@/modules/books/BooksLayout"
;
apps/admin/src/pages/books/management/addedit/index.jsx
浏览文件 @
77cf6ebc
import
{
useState
,
useEffect
}
from
"react"
;
import
{
Button
,
Form
,
Input
,
Flex
,
message
}
from
"antd"
;
import
WangEditor
from
"@/components/editor"
;
import
UploadImage
from
"@/components/uploadImage"
;
import
{
useLocation
,
useNavigate
}
from
"react-router"
;
import
{
addBook
,
updateBook
,
getBook
}
from
"../api"
;
import
SelectTeacher
from
"@/components/selectTeacher"
;
const
BookAddEdit
=
()
=>
{
const
navigate
=
useNavigate
();
const
location
=
useLocation
();
const
isEdit
=
!!
location
.
state
?.
id
;
const
[
form
]
=
Form
.
useForm
();
const
[
img
,
setImg
]
=
useState
(
""
);
// 编辑器内容
const
[
html
,
setHtml
]
=
useState
(
""
);
// 详情信息
const
fetchInfo
=
async
()
=>
{
const
{
data
}
=
await
getBook
({
id
:
location
.
state
.
id
});
setImg
(
data
.
img
);
setHtml
(
data
.
content
);
const
formFields
=
{
...
data
,
editors
:
data
.
editors_list
.
map
((
item
)
=>
{
return
{
label
:
item
.
real_name
,
value
:
item
.
editor_id
};
}),
};
form
.
setFieldsValue
(
formFields
);
};
useEffect
(()
=>
{
if
(
isEdit
)
fetchInfo
();
},
[
isEdit
]);
const
onFinish
=
async
(
values
)
=>
{
const
editors
=
values
.
editors
?.
map
((
item
)
=>
item
.
value
)
||
[];
const
params
=
{
...
values
,
img
,
editors
};
if
(
editors
.
length
===
0
)
delete
params
.
editors
;
const
res
=
isEdit
?
await
updateBook
({
id
:
location
.
state
.
id
,
...
params
})
:
await
addBook
(
params
);
message
.
success
(
res
.
message
);
navigate
(
-
1
);
};
return
(
<
Form
form=
{
form
}
onFinish=
{
onFinish
}
labelCol=
{
{
span
:
2
}
}
>
<
Form
.
Item
label=
"书籍名称"
name=
"name"
rules=
{
[
{
required
:
true
,
message
:
"请输入书籍名称"
},
{
max
:
30
,
message
:
"最大30字符"
},
]
}
>
<
Input
autoComplete=
"off"
placeholder=
"请输入书籍名称"
style=
{
{
width
:
260
}
}
></
Input
>
</
Form
.
Item
>
<
Form
.
Item
label=
"作者"
name=
"authors"
rules=
{
[
{
required
:
true
,
message
:
"请输入作者"
},
{
max
:
30
,
message
:
"最大30字符"
},
]
}
>
<
Input
autoComplete=
"off"
placeholder=
"请输入作者"
style=
{
{
width
:
260
}
}
></
Input
>
</
Form
.
Item
>
<
Form
.
Item
name=
"img"
label=
"缩略图"
className=
"uploadPar"
>
<
UploadImage
value=
{
img
}
onChange=
{
(
url
)
=>
setImg
(
url
)
}
></
UploadImage
>
</
Form
.
Item
>
<
Form
.
Item
label=
"编辑推荐"
name=
"introduction"
rules=
{
[{
max
:
65535
,
message
:
"最多65535字符"
}]
}
>
<
Input
.
TextArea
placeholder=
"请输入编辑推荐"
autoSize=
{
{
maxRows
:
8
,
minRows
:
5
}
}
style=
{
{
width
:
910
}
}
/>
</
Form
.
Item
>
<
Form
.
Item
label=
"编写者"
name=
"editors"
>
<
SelectTeacher
placeholder=
"请选择编写者"
style=
{
{
width
:
260
}
}
></
SelectTeacher
>
</
Form
.
Item
>
<
Form
.
Item
label=
"简介"
name=
"content"
>
<
WangEditor
value=
{
html
}
onChange=
{
(
editor
)
=>
form
.
setFieldValue
(
"content"
,
editor
.
getHtml
())
}
></
WangEditor
>
</
Form
.
Item
>
<
Form
.
Item
wrapperCol=
{
{
span
:
24
}
}
>
<
Flex
gap=
{
20
}
justify=
"center"
>
<
Button
onClick=
{
()
=>
{
navigate
(
-
1
);
}
}
>
取消
</
Button
>
<
Button
type=
"primary"
htmlType=
"submit"
>
确认
</
Button
>
</
Flex
>
</
Form
.
Item
>
</
Form
>
);
};
export
default
BookAddEdit
;
export
{
default
}
from
"@/modules/books/management/components/ManagementAddEdit"
;
apps/admin/src/pages/books/sale/discussDetail/index.jsx
浏览文件 @
77cf6ebc
import
{
Card
,
Input
,
Button
,
Space
,
Row
,
Col
,
Modal
,
Form
}
from
"antd"
;
import
{
DeleteOutlined
,
FacebookFilled
,
MessageOutlined
}
from
"@ant-design/icons"
;
import
"./index.less"
;
import
User
from
"@/assets/images/icon/user.png"
;
import
{
useState
,
useEffect
}
from
"react"
;
import
{
useLocation
}
from
"react-router"
;
import
{
getCommentInfo
,
commentReply
,
commentDel
}
from
"../request"
;
const
{
TextArea
}
=
Input
;
const
DiscussDetail
=
()
=>
{
const
[
showReply
,
setShowReply
]
=
useState
(
false
);
const
[
delMoadal
,
setDelModal
]
=
useState
(
false
);
const
{
state
:
{
id
,
name
,
book_id
},
}
=
useLocation
();
const
[
form
]
=
Form
.
useForm
();
const
[
data
,
setData
]
=
useState
({});
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
commentId
,
setcommentId
]
=
useState
(
0
);
// 获取列表
const
init
=
async
()
=>
{
setLoading
(
true
);
const
data
=
await
getCommentInfo
({
id
,
book_id
});
console
.
log
(
data
);
setData
(
data
);
setLoading
(
false
);
};
useEffect
(()
=>
{
init
();
},
[]);
useEffect
(()
=>
{
if
(
!
showReply
)
{
form
.
setFieldsValue
({
content
:
""
});
form
.
resetFields
();
}
},
[
showReply
]);
// 显示回复弹框
const
showReplyFn
=
(
id
)
=>
{
setcommentId
(
id
);
setShowReply
(
true
);
};
// 点击确认回复
const
handleReply
=
async
(
obj
)
=>
{
const
bool
=
await
commentReply
({
...
obj
,
id
:
commentId
,
book_id
});
if
(
!
bool
)
return
;
setShowReply
(
false
);
init
();
};
const
handleDel
=
async
()
=>
{
const
bool
=
await
commentDel
({
id
:
commentId
,
book_id
});
if
(
!
bool
)
return
;
setDelModal
(
false
);
init
();
};
return
(
<
div
className=
"DiscussDetail"
>
<
Modal
open=
{
showReply
}
onCancel=
{
()
=>
{
setShowReply
(
false
);
}
}
maskClosable=
{
false
}
footer=
{
null
}
centered
closeIcon=
{
null
}
keyboard=
{
false
}
>
<
br
/>
<
Form
form=
{
form
}
onFinish=
{
handleReply
}
>
<
Form
.
Item
label=
"内容"
name=
"content"
rules=
{
[{
required
:
true
,
message
:
"请输入内容"
}]
}
>
<
TextArea
placeholder=
"请输入内容"
autoSize=
{
{
minRows
:
3
,
maxRows
:
5
,
}
}
></
TextArea
>
</
Form
.
Item
>
<
Form
.
Item
>
<
Row
gutter=
{
20
}
justify=
{
"center"
}
>
<
Col
>
<
Button
onClick=
{
()
=>
setShowReply
(
false
)
}
>
取消
</
Button
>
</
Col
>
<
Col
>
<
Button
type=
"primary"
htmlType=
"submit"
>
确认
</
Button
>
</
Col
>
</
Row
>
</
Form
.
Item
>
</
Form
>
</
Modal
>
<
Modal
mask=
{
false
}
centered
open=
{
delMoadal
}
onOk=
{
handleDel
}
onCancel=
{
()
=>
setDelModal
(
false
)
}
footer=
{
(
_
,
{
OkBtn
,
CancelBtn
})
=>
{
return
(
<
Row
justify=
{
"center"
}
className=
"delModal"
>
<
Space
>
<
CancelBtn
/>
<
OkBtn
/>
</
Space
>
</
Row
>
);
}
}
okText=
"确认"
>
<
p
style=
{
{
textAlign
:
"center"
,
padding
:
"50px 0 30px"
,
fontSize
:
16
}
}
>
评论将永久删除,是否继续?
</
p
>
</
Modal
>
<
Card
className=
"card"
title=
{
`书名:《${name}》`
}
loading=
{
loading
}
>
<
div
className=
"discussBox"
>
<
Row
className=
"discussItem"
gutter=
{
10
}
>
<
Col
className=
"img"
>
<
img
src=
{
data
.
person_pic
||
User
}
width=
{
28
}
height=
{
28
}
style=
{
{
borderRadius
:
"50%"
,
objectFit
:
"cover"
}
}
/>
</
Col
>
<
Col
className=
"discuss"
flex=
{
1
}
>
<
div
className=
"name"
>
{
data
.
person_name
}
</
div
>
{
/* <div className='content'>{data?.content && data.content[0].content}</div> */
}
<
div
className=
"content"
>
{
data
?.
content
&&
data
.
content
.
map
((
item
,
index
)
=>
{
const
type
=
item
.
types
;
const
value
=
item
.
content
;
if
(
type
===
"audio"
)
{
return
(
<
div
key=
{
index
}
>
<
span
>
<
audio
controls
>
<
source
src=
{
value
}
type=
"audio/mp3"
/>
Your browser does not support the audio element.
</
audio
>
</
span
>
</
div
>
);
}
else
if
(
type
===
"text"
)
{
return
<
span
key=
{
index
}
>
{
value
}
</
span
>;
}
else
if
(
type
===
"image"
)
{
return
<
img
height=
{
100
}
src=
{
value
}
></
img
>;
}
})
}
</
div
>
<
Space
>
<
Button
type=
"text"
className=
"reply"
onClick=
{
()
=>
showReplyFn
(
data
.
id
)
}
icon=
{
<
MessageOutlined
/>
}
>
回复
</
Button
>
<
Button
type=
"text"
className=
"del"
icon=
{
<
DeleteOutlined
/>
}
onClick=
{
()
=>
{
setDelModal
(
true
);
setcommentId
(
data
.
id
);
}
}
>
删除
</
Button
>
</
Space
>
</
Col
>
</
Row
>
{
data
.
child
&&
data
.
child
.
map
((
item
)
=>
(
<
Row
className=
"discussItem reply"
key=
{
item
.
id
}
gutter=
{
10
}
>
<
Col
span=
{
1
}
></
Col
>
<
Col
className=
"img"
>
<
img
src=
{
item
.
person_pic
||
User
}
width=
{
28
}
height=
{
28
}
style=
{
{
borderRadius
:
"50%"
,
objectFit
:
"cover"
}
}
/>
</
Col
>
<
Col
className=
"discuss"
flex=
{
1
}
>
<
div
className=
"name"
>
{
item
.
person_name
}
</
div
>
<
div
className=
"content"
>
{
item
.
content
&&
item
.
content
.
map
((
item
,
index
)
=>
(
<
div
key=
{
index
}
>
{
item
.
types
==
"text"
&&
<
span
>
{
item
.
content
}
</
span
>
}
{
item
.
types
==
"image"
&&
<
img
src=
{
item
.
content
}
></
img
>
}
{
item
.
types
==
"audio"
&&
<
audio
controls
src=
{
item
.
content
}
/>
}
</
div
>
))
}
</
div
>
<
Space
>
<
Button
type=
"text"
className=
"reply"
onClick=
{
()
=>
showReplyFn
(
item
.
id
)
}
icon=
{
<
MessageOutlined
/>
}
>
回复
</
Button
>
<
Button
type=
"text"
className=
"del"
icon=
{
<
DeleteOutlined
/>
}
onClick=
{
()
=>
{
setDelModal
(
true
);
setcommentId
(
item
.
id
);
}
}
>
删除
</
Button
>
</
Space
>
</
Col
>
</
Row
>
))
}
</
div
>
{
/* <div className="discussCreate">
<Row gutter={10}>
<Col><div className="img"><img src={User} width={28} height={28} /></div></Col>
<Col><Input autoComplete="off" placeholder="发布你的评论" style={{width:754}}></Input ></Col>
<Col>
<Space>
<Button>取消</Button>
<Button type="primary">回复</Button>
</Space>
</Col>
</Row>
</div> */
}
</
Card
>
</
div
>
);
};
export
default
DiscussDetail
;
export
{
default
}
from
"@/modules/books/sale/components/SaleDiscussionDetail"
;
apps/admin/src/pages/books/sale/discussDetail/index.less
浏览文件 @
77cf6ebc
.DiscussDetail {
.reply {
color: #aa1941;
}
.del {
color: #999999;
}
.card {
.ant-card-body {
height: calc(100vh - 260px);
overflow: auto;
}
}
.discussItem {
.discuss {
&::after {
display: block;
content: "";
width: 100%;
height: 1px;
background: #e5e5e57f;
margin-top: 16px;
margin-bottom: 10px;
}
.name {
font-size: 12px;
color: #333333;
font-weight: 500;
margin-bottom: 4px;
}
.content {
font-size: 12px;
color: #333333;
}
}
}
}
@import "@/modules/books/sale/components/SaleDiscussionDetail.less";
apps/admin/src/pages/books/sale/edit.jsx
浏览文件 @
77cf6ebc
import
React
,
{
useEffect
,
useState
,
useRef
}
from
"react"
;
import
{
Form
,
Input
,
Select
,
Button
,
Radio
,
Space
,
Image
,
Spin
,
Tree
,
Drawer
,
Row
,
Col
,
message
,
}
from
"antd"
;
import
{
getInfoById
,
publisherList
,
publisherAdd
,
getListAll
,
editBook
}
from
"./request"
;
import
{
getList
}
from
"../classify/request"
;
import
{
useLocation
}
from
"react-router"
;
import
{
useNavigate
}
from
"react-router"
;
import
{
convertToAntdTreeData
}
from
"@/utils/common.js"
;
const
SaleEdit
=
()
=>
{
const
{
state
:
{
id
},
}
=
useLocation
();
const
[
initialValues
,
setinitialValues
]
=
useState
({
name
:
""
,
authors
:
""
,
category_id
:
null
,
producers_id
:
null
,
press_id
:
null
,
is_free
:
null
,
});
const
[
form
]
=
Form
.
useForm
();
const
navigate
=
useNavigate
();
const
[
showAddCategory
,
setShowAddCategory
]
=
useState
(
false
);
//弹出添加出品方
const
[
showAddCategory1
,
setShowAddCategory1
]
=
useState
(
false
);
const
[
showDrawer
,
setShowDrawer
]
=
useState
(
false
);
const
[
publisher_name
,
setPublisherName
]
=
useState
(
""
);
//出品方
const
[
publishers
,
setPublishers
]
=
useState
([]);
const
[
publisher_name1
,
setPublisherName1
]
=
useState
(
""
);
//出版社
const
[
publishers1
,
setPublishers1
]
=
useState
([]);
const
[
treeData
,
setTreeData
]
=
useState
([]);
// 在组件中添加一个状态来保存书籍类型的值
const
[
bookType
,
setBookType
]
=
useState
(
0
);
// 默认值为免费
const
[
producersConfig
,
setproducersConfig
]
=
useState
({
validateStatus
:
""
,
help
:
""
,
noStyle
:
false
,
style
:
{},
});
const
[
pressConfig
,
setpressConfig
]
=
useState
({
validateStatus
:
""
,
help
:
""
,
noStyle
:
false
,
style
:
{},
});
const
addproducers
=
async
(
obj
,
callbackFn
)
=>
{
const
{
types
}
=
obj
;
let
flag
=
true
;
if
(
types
==
1
)
{
if
(
!
publisher_name
)
{
flag
=
false
;
setproducersConfig
({
...
producersConfig
,
validateStatus
:
"error"
,
help
:
"请输入新的出品方"
,
noStyle
:
false
,
});
}
}
else
if
(
types
==
2
)
{
if
(
!
publisher_name1
)
{
flag
=
false
;
setpressConfig
({
...
pressConfig
,
validateStatus
:
"error"
,
help
:
"请输入新的出版社"
,
noStyle
:
false
,
});
}
}
if
(
!
flag
)
return
;
// 上面代码是校验
const
bool
=
await
publisherAdd
(
obj
);
if
(
!
bool
)
return
;
callbackFn
();
setproducersConfig
({
validateStatus
:
""
,
help
:
""
,
noStyle
:
false
,
style
:
{}
});
setpressConfig
({
validateStatus
:
""
,
help
:
""
,
noStyle
:
false
,
style
:
{}
});
getProducers
();
};
const
[
price
,
setprice
]
=
useState
(
0
);
const
[
vip_price
,
setvip_price
]
=
useState
(
0
);
const
[
discountVal
,
setDiscountVal
]
=
useState
(
0
);
const
calculateDiscountVal
=
(
price
,
vip_price
)
=>
{
if
(
Number
(
price
)
>
0
&&
Number
(
vip_price
)
>
0
)
{
return
(
Number
(
vip_price
)
/
Number
(
price
)).
toFixed
(
2
)
*
10
;
}
else
{
return
0
;
}
};
// 只在零售价改变时更新折扣率
useEffect
(()
=>
{
let
price
=
form
.
getFieldValue
(
"price"
);
let
vip_price
=
form
.
getFieldValue
(
"vip_price"
);
setDiscountVal
(
calculateDiscountVal
(
price
,
vip_price
).
toFixed
(
2
));
console
.
log
(
"discountVal"
,
discountVal
);
},
[
form
.
getFieldValue
(
"price"
),
form
.
getFieldValue
(
"vip_price"
)]);
// // 问题:输入3.3折后,输入框变成3.30000 ; 原因:toFixed()数字转字符串时可能会导致精度问题 ; 解决:parseFloat来解析浮点数
// const handleDiscountRateChange = (ev) => {
// const newVal = ev.target.value;
// if (Number(newVal) >= 0 && Number(newVal) <= 10) {
// setDiscountVal(newVal);
// const price = form.getFieldValue('price');
// updateVipPriceFromDiscountRate(price, newVal);
// }
// };
// const updateVipPriceFromDiscountRate = (price, discountRate) => {
// if (Number(price) > 0 && Number(discountRate) >= 0 && Number(discountRate) <= 10) {
// const vip_price = price * (discountRate / 10);
// form.setFieldsValue({ vip_price: vip_price.toFixed(2) });
// }
// };
// 在onChange中用parseFloat来解析浮点数,输入折扣数后,就触发折扣价改变,发现还是不太行,因为浮点数的精度问题可能会导致在 onChange 事件中出现意外行为。目前会更报错出现NaN,当在用户输入小数点和小数部分时,浮点数可能会被不完整地解析,导致计算错误,出现NaN
// const handleDiscountRateChange = (ev) => {
// const newVal = ev.target.value;
// if (Number(newVal) >= 0 && Number(newVal) <= 10) {
// const roundedVal = parseFloat(newVal).toFixed(2); // 对输入的值进行四舍五入处理
// setDiscountVal(roundedVal);
// const price = form.getFieldValue('price');
// // 添加计算并更新折扣金额的逻辑
// if (Number(price) > 0) {
// const vip_price = price * (roundedVal / 10);
// form.setFieldsValue({ vip_price: vip_price.toFixed(2) });
// }
// }
// };
// 将parseFloat放在失去焦点(onBlur)中处理,更靠谱
const
handleDiscountRateChange
=
(
ev
)
=>
{
const
newVal
=
ev
.
target
.
value
;
if
(
Number
(
newVal
)
>=
0
&&
Number
(
newVal
)
<=
10
&&
/^
\d
*
\.?\d{0,3}
$/
.
test
(
newVal
))
{
// 不在这里进行四舍五入处理,而是在失去焦点时处理
setDiscountVal
(
newVal
);
}
else
{
message
.
error
(
"最多输入小数点后3位,且不能大于10折"
);
newVal
.
slice
(
0
,
-
1
);
}
};
const
handleDiscountRateBlur
=
()
=>
{
const
newVal
=
parseFloat
(
discountVal
);
if
(
Number
.
isFinite
(
newVal
)
&&
newVal
>=
0
&&
newVal
<=
10
)
{
const
roundedVal
=
newVal
.
toFixed
(
3
);
setDiscountVal
(
roundedVal
);
const
price
=
form
.
getFieldValue
(
"price"
);
// 添加计算并更新折扣元的逻辑
if
(
Number
(
price
)
>
0
)
{
const
vip_price
=
price
*
(
roundedVal
/
10
);
form
.
setFieldsValue
({
vip_price
:
vip_price
.
toFixed
(
2
)
});
}
}
};
const
handleVippriceChange
=
(
ev
)
=>
{
const
newVal
=
ev
.
target
.
value
;
let
price
=
form
.
getFieldValue
(
"price"
);
if
(
Number
(
newVal
)
<=
price
&&
/^
\d
*
\.?\d{0,3}
$/
.
test
(
newVal
))
{
setvip_price
(
newVal
);
}
else
{
message
.
error
(
"最多输入小数点后3位,且不能大于零售价"
);
newVal
.
slice
(
0
,
-
1
);
}
};
useEffect
(()
=>
{
if
(
!
showAddCategory
)
{
setPublisherName
(
""
);
}
if
(
!
showAddCategory1
)
{
setPublisherName1
(
""
);
}
},
[
showAddCategory
,
showAddCategory1
]);
const
handleOpenDrawer
=
async
()
=>
{
setShowDrawer
(
true
);
const
data
=
await
getListAll
({
book_id
:
id
});
let
arr
=
convertToAntdTreeData
(
data
,
"name"
);
setTreeData
(
arr
);
};
const
handleCloseDrawer
=
()
=>
{
setShowDrawer
(
false
);
};
const
[
classData
,
setClassData
]
=
useState
([]);
const
[
producersData
,
setproducersData
]
=
useState
([]);
const
[
pressData
,
setpressData
]
=
useState
([]);
// 出品方/出版社
const
getProducers
=
async
()
=>
{
const
producersList
=
await
publisherList
({
types
:
1
});
const
press
=
await
publisherList
({
types
:
2
});
setproducersData
(
producersList
);
setpressData
(
press
);
};
const
[
FormLoad
,
setFormLoad
]
=
useState
(
true
);
const
[
checkedKeys
,
setcheckedKeys
]
=
useState
([]);
const
submitTree
=
(
obj
)
=>
{
form
.
setFieldValue
(
"trials"
,
checkedKeys
);
handleCloseDrawer
(
false
);
};
const
ontreeCheck
=
(
keys
,
{
checked
,
checkedNodes
,
node
,
halfCheckedKeys
})
=>
{
setcheckedKeys
(
keys
);
/* const handleCheck = (parentNode) => {
const keys = [];
const traverse = (currentNode) => {
keys.push(currentNode.key);
if (currentNode.children) {
currentNode.children.forEach((childNode) => {
traverse(childNode, currentNode.key);
});
}
};
traverse(parentNode);
return keys;
};
if (checked) {
const keysToCheck = handleCheck(node);
setcheckedKeys([...checkedKeys.checked, ...keysToCheck]);
} else {
const keysToUncheck = handleCheck(node);
const newCheckedKeys = checkedKeys.checked.filter((key) => !keysToUncheck.includes(key));
setcheckedKeys(newCheckedKeys);
} */
};
const
getinfo
=
async
()
=>
{
setFormLoad
(
true
);
// 详情
const
data
=
await
getInfoById
({
id
});
const
{
category_id
,
producers_id
,
press_id
}
=
data
;
let
obj
=
{
category_id
,
producers_id
,
press_id
};
setBookType
(
data
.
is_free
);
for
(
let
key
in
obj
)
{
if
(
obj
[
key
]
===
0
)
{
obj
[
key
]
=
null
;
}
}
form
.
setFieldsValue
({
...
data
,
...
obj
});
setFormLoad
(
false
);
};
const
getInfo
=
async
()
=>
{
// 分类
const
{
list
}
=
await
getList
({
page
:
1
,
page_size
:
999
});
setClassData
(
list
);
getProducers
();
getinfo
();
};
const
onFinish
=
async
(
obj
)
=>
{
const
bool
=
await
editBook
({
...
obj
,
id
});
if
(
!
bool
)
return
;
navigate
(
-
1
);
};
useEffect
(()
=>
{
getInfo
();
},
[]);
return
(
<
Spin
spinning=
{
FormLoad
}
style=
{
{
padding
:
20
}
}
>
<
Form
labelCol=
{
{
span
:
2
}
}
form=
{
form
}
initialValues=
{
initialValues
}
onFinish=
{
onFinish
}
>
<
Form
.
Item
label=
"书籍名称"
name=
"name"
>
<
Input
disabled
allowClear
autoComplete=
"off"
placeholder=
"请输入书籍名称"
style=
{
{
width
:
400
}
}
></
Input
>
</
Form
.
Item
>
<
Form
.
Item
label=
"书籍作者"
name=
"authors"
>
<
Input
autoComplete=
"off"
disabled
style=
{
{
width
:
260
}
}
></
Input
>
</
Form
.
Item
>
<
Form
.
Item
label=
"分类"
name=
"category_id"
rules=
{
[{
required
:
true
,
message
:
"请选择分类"
}]
}
>
<
Select
placeholder=
"请选择分类"
style=
{
{
width
:
260
}
}
>
{
classData
&&
classData
.
map
((
item
)
=>
(
<
Select
.
Option
value=
{
item
.
id
}
key=
{
item
.
id
}
>
{
item
.
name
}
</
Select
.
Option
>
))
}
</
Select
>
</
Form
.
Item
>
<
Form
.
Item
label=
"出品方"
required
style=
{
producersConfig
.
style
}
>
<
Space
>
<
Form
.
Item
noStyle
name=
"producers_id"
rules=
{
[{
required
:
true
,
message
:
"请选择出品方"
}]
}
>
<
Select
placeholder=
"请选择出品方"
style=
{
{
width
:
260
}
}
>
{
producersData
?.
map
((
item
)
=>
(
<
Select
.
Option
key=
{
item
.
id
}
value=
{
item
.
id
}
>
{
item
.
publisher_name
}
</
Select
.
Option
>
))
}
</
Select
>
</
Form
.
Item
>
<
a
style=
{
{
textDecoration
:
"underline"
,
color
:
"#1672EC"
,
cursor
:
"pointer"
,
}
}
onClick=
{
()
=>
{
setproducersConfig
({
...
producersConfig
,
style
:
{
marginBottom
:
0
}
});
setShowAddCategory
(
true
);
}
}
>
添加出品方
</
a
>
</
Space
>
{
showAddCategory
&&
(
<
div
style=
{
{
marginTop
:
"10px"
}
}
>
<
Space
>
<
Form
.
Item
noStyle=
{
producersConfig
.
noStyle
}
validateStatus=
{
producersConfig
.
validateStatus
}
help=
{
producersConfig
.
help
}
>
<
Input
style=
{
{
width
:
260
}
}
autoComplete=
"off"
placeholder=
"请输入新的出品方"
value=
{
publisher_name
}
allowClear
onChange=
{
(
e
)
=>
{
let
val
=
e
.
target
.
value
;
if
(
val
)
{
setproducersConfig
({
...
producersConfig
,
help
:
""
,
validateStatus
:
""
});
}
setPublisherName
(
val
);
}
}
/>
</
Form
.
Item
>
<
a
type=
"link"
style=
{
{
textDecoration
:
"underline"
,
color
:
"#1672EC"
}
}
onClick=
{
()
=>
addproducers
({
types
:
1
,
publisher_name
},
()
=>
setShowAddCategory
(
false
))
}
>
添加
</
a
>
<
a
type=
"link"
style=
{
{
textDecoration
:
"underline"
,
color
:
"#AA1941"
}
}
onClick=
{
()
=>
{
setproducersConfig
({
validateStatus
:
""
,
help
:
""
,
noStyle
:
false
,
style
:
{}
});
setShowAddCategory
(
false
);
// 可选:添加后关闭输入框
}
}
>
删除
</
a
>
</
Space
>
</
div
>
)
}
</
Form
.
Item
>
<
Form
.
Item
label=
"出版社"
required
style=
{
pressConfig
.
style
}
>
<
Space
>
<
Form
.
Item
noStyle
name=
"press_id"
rules=
{
[{
required
:
true
,
message
:
"请选择出版社"
}]
}
>
<
Select
placeholder=
"请选择出品方"
style=
{
{
width
:
260
}
}
>
{
pressData
?.
map
((
item
)
=>
{
return
(
<
Select
.
Option
key=
{
item
.
id
}
value=
{
item
.
id
}
>
{
item
.
publisher_name
}
</
Select
.
Option
>
);
})
}
</
Select
>
</
Form
.
Item
>
<
a
style=
{
{
textDecoration
:
"underline"
,
color
:
"#1672EC"
,
cursor
:
"pointer"
,
}
}
onClick=
{
()
=>
{
setpressConfig
({
...
pressConfig
,
style
:
{
marginBottom
:
0
}
});
setShowAddCategory1
(
true
);
}
}
>
添加出版社
</
a
>
</
Space
>
{
showAddCategory1
&&
(
<
div
style=
{
{
marginTop
:
"10px"
}
}
>
<
Space
>
<
Form
.
Item
noStyle=
{
pressConfig
.
noStyle
}
validateStatus=
{
pressConfig
.
validateStatus
}
help=
{
pressConfig
.
help
}
>
<
Input
style=
{
{
width
:
260
}
}
autoComplete=
"off"
placeholder=
"请输入新的出版社"
allowClear
value=
{
publisher_name1
}
onChange=
{
(
e
)
=>
{
let
val
=
e
.
target
.
value
;
if
(
val
)
{
setpressConfig
({
...
pressConfig
,
help
:
""
,
validateStatus
:
""
});
}
setPublisherName1
(
e
.
target
.
value
);
}
}
/>
</
Form
.
Item
>
<
a
type=
"link"
style=
{
{
textDecoration
:
"underline"
,
color
:
"#1672EC"
}
}
onClick=
{
()
=>
addproducers
({
types
:
2
,
publisher_name
:
publisher_name1
},
()
=>
setShowAddCategory1
(
false
),
)
}
>
添加
</
a
>
<
a
type=
"link"
style=
{
{
textDecoration
:
"underline"
,
color
:
"#AA1941"
}
}
onClick=
{
()
=>
{
setpressConfig
({
validateStatus
:
""
,
help
:
""
,
noStyle
:
false
,
style
:
{}
});
setShowAddCategory1
(
false
);
}
}
>
删除
</
a
>
</
Space
>
</
div
>
)
}
</
Form
.
Item
>
<
Form
.
Item
label=
"书籍类型"
name=
"is_free"
>
<
Radio
.
Group
onChange=
{
(
e
)
=>
{
setBookType
(
e
.
target
.
value
);
}
}
>
<
Radio
value=
{
1
}
>
免费
</
Radio
>
<
Radio
value=
{
0
}
>
付费
</
Radio
>
</
Radio
.
Group
>
</
Form
.
Item
>
{
bookType
===
0
&&
(
<
Form
.
Item
label=
"试读章节"
name=
"trials"
>
<
a
type=
"link"
style=
{
{
textDecoration
:
"underline"
}
}
onClick=
{
handleOpenDrawer
}
>
选择试读章节
</
a
>
</
Form
.
Item
>
)
}
{
bookType
===
0
&&
(
<>
<
Form
.
Item
label=
"零售价(元):"
name=
"price"
>
<
Input
value=
{
price
}
onChange=
{
(
e
)
=>
setprice
(
e
.
target
.
value
)
}
autoComplete=
"off"
allowClear
style=
{
{
width
:
80
}
}
></
Input
>
</
Form
.
Item
>
<
Form
.
Item
label=
"折扣价(元):"
>
<
Form
.
Item
name=
"vip_price"
style=
{
{
display
:
"inline-block"
}
}
>
<
Input
value=
{
vip_price
}
// onChange=
{(
e
)
=
>
setvip_price(e.target.value)}
onChange=
{
handleVippriceChange
}
autoComplete="off"
allowClear
style=
{
{
width
:
80
}
}
>
</
Input
>
</
Form
.
Item
>
<
Form
.
Item
style=
{
{
display
:
"inline-block"
,
margin
:
"0 8px"
}
}
>
<
Input
// value=
{
Number
(
discountVal
)
.
toFixed
(2)}
value=
{
discountVal
}
allowClear
onChange=
{
handleDiscountRateChange
}
onBlur=
{
handleDiscountRateBlur
}
// 添加失去焦点事件处理函数
autoComplete=
"off"
style=
{
{
width
:
80
}
}
></
Input
>
折
</
Form
.
Item
>
</
Form
.
Item
>
</>
)
}
<
Form
.
Item
wrapperCol=
{
{
offset
:
2
}
}
>
<
Space
className=
"footer"
size=
{
20
}
>
<
Button
onClick=
{
()
=>
{
navigate
(
-
1
);
}
}
>
取消
</
Button
>
<
Button
type=
"primary"
htmlType=
"submit"
>
确认
</
Button
>
</
Space
>
</
Form
.
Item
>
</
Form
>
<
Drawer
placement=
"right"
onClose=
{
handleCloseDrawer
}
open=
{
showDrawer
}
labelCol=
{
{
span
:
7
}
}
mask=
{
false
}
>
<
Form
onFinish=
{
submitTree
}
>
<
Form
.
Item
name=
"power_list"
style=
{
{
padding
:
10
}
}
>
<
Tree
checkable
checkedKeys=
{
checkedKeys
}
defaultExpandAll=
{
false
}
//让授权后的弹窗只展示根标签
treeData=
{
treeData
}
// showLine //删除这里,树形结构左侧的下拉线消失,图标从+-更改为默认的△
// checkStrictly
onCheck=
{
ontreeCheck
}
/>
</
Form
.
Item
>
<
Form
.
Item
wrapperCol=
{
{
offset
:
10
,
span
:
16
}
}
>
<
Space
size=
{
20
}
>
<
Button
onClick=
{
()
=>
handleCloseDrawer
(
false
)
}
>
取消
</
Button
>
<
Button
type=
"primary"
htmlType=
"submit"
>
提交
</
Button
>
</
Space
>
</
Form
.
Item
>
</
Form
>
</
Drawer
>
</
Spin
>
);
};
export
default
SaleEdit
;
export
{
default
}
from
"@/modules/books/sale/components/SaleEdit"
;
apps/admin/src/pages/books/section/api.js
浏览文件 @
77cf6ebc
import
axios
from
"@/utils/axios"
;
// 获取书籍详情
export
function
getBook
(
data
)
{
return
axios
.
post
(
"/api/book/teacher/getInfoById"
,
data
);
}
// 获取章节列表
export
function
getChapterList
(
data
)
{
return
axios
.
post
(
"/api/book/teacher/chapter/getAllList"
,
data
);
}
// 获取章节详情
export
function
getChapter
(
data
)
{
return
axios
.
post
(
"/api/book/teacher/chapter/getInfoById"
,
data
);
}
// 添加章节
export
function
addChapter
(
data
)
{
return
axios
.
post
(
"/api/book/teacher/chapter/add"
,
data
);
}
// 修改章节
export
function
updateChapter
(
data
)
{
return
axios
.
post
(
"/api/book/teacher/chapter/edit"
,
data
);
}
// 删除章节
export
function
delChapter
(
data
)
{
return
axios
.
post
(
"/api/book/teacher/chapter/del"
,
data
);
}
// 章节排序
export
function
sortChapter
(
data
)
{
return
axios
.
post
(
"/api/book/teacher/chapter/dragOrder"
,
data
);
}
// 获取章节编辑者信息
export
function
getChapterEditors
(
data
)
{
return
axios
.
post
(
"/api/book/teacher/chapter/getEditors"
,
data
);
}
// 保存章节编辑者
export
function
updateChapterEditors
(
data
)
{
return
axios
.
post
(
"/api/book/teacher/chapter/saveEditors"
,
data
);
}
// 知识图谱列表
export
function
getTagList
(
params
)
{
return
axios
.
get
(
"/api/resource/v1/course/course/tag-list"
,
{
params
});
}
// 知识图谱关联书籍章节
export
function
bindTag
(
data
)
{
return
axios
.
post
(
"/api/resource/v1/course/course/tag-add-book"
,
data
);
}
export
*
from
"@/modules/books/management/chapters/api"
;
apps/admin/src/pages/books/section/components/BindKnowledgeGraph.jsx
浏览文件 @
77cf6ebc
import
{
useState
,
useEffect
,
useCallback
}
from
"react"
;
import
{
Modal
,
Tree
,
App
,
Button
,
Flex
,
Empty
}
from
"antd"
;
import
{
getTagList
,
bindTag
}
from
"../api"
;
function
getIds
(
nodes
)
{
let
ids
=
[];
nodes
.
forEach
((
node
)
=>
{
ids
.
push
(
node
.
id
);
if
(
node
.
children
&&
node
.
children
.
length
>
0
)
{
ids
=
ids
.
concat
(
getIds
(
node
.
children
));
}
});
return
ids
;
}
const
BindKnowledgeGraph
=
({
chapter
=
{},
...
props
})
=>
{
const
{
message
}
=
App
.
useApp
();
const
[
treeData
,
setTreeData
]
=
useState
([]);
const
[
expandedKeys
,
setExpandedKeys
]
=
useState
([]);
const
fetchList
=
useCallback
(()
=>
{
getTagList
({
type
:
2
,
parent_id
:
chapter
.
book_id
,
chapter_id
:
chapter
.
id
}).
then
((
res
)
=>
{
setTreeData
(
res
.
data
);
setExpandedKeys
(
getIds
(
res
.
data
));
});
},
[
chapter
]);
useEffect
(()
=>
{
fetchList
();
},
[
fetchList
]);
const
toggleBind
=
async
(
node
)
=>
{
await
bindTag
({
tag_id
:
node
.
id
,
book_id
:
chapter
.
book_id
,
chapter_id
:
chapter
.
id
,
option_type
:
node
.
is_checked
?
"delete"
:
"add"
,
});
message
.
success
(
"操作成功"
);
fetchList
();
};
const
titleRender
=
(
node
)
=>
{
return
(
<
Flex
justify=
"space-between"
>
<
span
>
{
node
.
name
}
</
span
>
<
Button
color=
"primary"
variant=
"link"
size=
"small"
onClick=
{
()
=>
toggleBind
(
node
)
}
>
{
node
.
is_checked
?
"取消关联"
:
"关联"
}
</
Button
>
</
Flex
>
);
};
return
(
<
Modal
title=
"关联知识图谱"
centered
{
...
props
}
footer=
{
null
}
>
{
treeData
.
length
>
0
?
(
<
Tree
blockNode
selectable=
{
false
}
treeData=
{
treeData
}
fieldNames=
{
{
title
:
"name"
,
key
:
"id"
}
}
expandedKeys=
{
expandedKeys
}
titleRender=
{
titleRender
}
onExpand=
{
setExpandedKeys
}
/>
)
:
(
<
Empty
image=
{
Empty
.
PRESENTED_IMAGE_SIMPLE
}
description=
"暂无知识图谱"
/>
)
}
</
Modal
>
);
};
export
default
BindKnowledgeGraph
;
export
{
default
}
from
"@/modules/books/management/chapters/components/BindKnowledgeGraph"
;
apps/admin/src/pages/books/section/components/EditChapterEditors.jsx
浏览文件 @
77cf6ebc
import
{
useState
,
useEffect
}
from
"react"
;
import
{
Modal
,
Form
,
Radio
,
Space
,
App
}
from
"antd"
;
import
{
getChapterEditors
,
updateChapterEditors
}
from
"../api"
;
const
EditChapterEditors
=
({
chapter
=
{},
onChange
,
...
props
})
=>
{
const
{
message
}
=
App
.
useApp
();
const
[
editors
,
setEditors
]
=
useState
([]);
const
[
value
,
setValue
]
=
useState
(
""
);
useEffect
(()
=>
{
getChapterEditors
({
book_id
:
chapter
.
book_id
,
chapter_id
:
chapter
.
id
}).
then
((
res
)
=>
{
setEditors
(
res
.
data
.
editors
||
[]);
const
value
=
res
.
data
.
selected_editor_ids
[
0
]
||
""
;
setValue
(
value
);
});
},
[
chapter
]);
const
handleSubmit
=
async
()
=>
{
await
updateChapterEditors
({
book_id
:
chapter
.
book_id
,
chapter_id
:
chapter
.
id
,
editor_id
:
value
,
});
message
.
success
(
"设置成功"
);
onChange
&&
onChange
();
};
return
(
<
Modal
title=
"设置章节编写者"
centered
{
...
props
}
onOk=
{
handleSubmit
}
>
<
Form
labelCol=
{
{
span
:
8
}
}
>
<
Form
.
Item
label=
"当前章节"
>
{
chapter
.
title
}
</
Form
.
Item
>
<
Form
.
Item
label=
"编写者"
>
<
Radio
.
Group
value=
{
value
}
onChange=
{
(
e
)
=>
setValue
(
e
.
target
.
value
)
}
>
<
Space
direction=
"vertical"
>
{
editors
.
map
((
item
)
=>
{
return
(
<
Radio
value=
{
item
.
editor_id
}
key=
{
item
.
editor_id
}
>
{
item
.
real_name
}
(
{
item
.
tel
}
)
</
Radio
>
);
})
}
</
Space
>
</
Radio
.
Group
>
</
Form
.
Item
>
</
Form
>
</
Modal
>
);
};
export
default
EditChapterEditors
;
export
{
default
}
from
"@/modules/books/management/chapters/components/EditChapterEditors"
;
apps/admin/src/pages/books/section/components/form-chapter-title.jsx
浏览文件 @
77cf6ebc
import
{
useState
,
useEffect
,
useRef
}
from
"react"
;
import
{
Form
,
Button
,
Space
,
Input
}
from
"antd"
;
import
{
setTreeChapter
}
from
"@/store/modules/user"
;
import
{
useDispatch
}
from
"react-redux"
;
import
{
chapterEdit
,
chapterAdd
,
sectionAdd
}
from
"../request"
;
const
EditChapterTitle
=
(
props
)
=>
{
const
{
editValue
,
setEditValue
,
editKey
,
setEditKey
,
parentId
,
bookId
,
getChapterTreeList
,
expandedKeys
,
setExpandedKeys
,
editorLoading
,
newChapterChange
,
}
=
props
;
const
dispatch
=
useDispatch
();
const
[
form
]
=
Form
.
useForm
();
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
initValues
,
setInitValues
]
=
useState
({
title
:
editValue
});
const
inputRef
=
useRef
();
useEffect
(()
=>
{
if
(
inputRef
.
current
)
{
inputRef
.
current
.
focus
();
}
},
[]);
useEffect
(()
=>
{
if
(
inputRef
.
current
)
{
inputRef
.
current
.
focus
();
}
},
[
editorLoading
]);
useEffect
(()
=>
{
form
.
setFieldsValue
({
title
:
editValue
});
setInitValues
({
title
:
editValue
});
},
[
editValue
]);
const
onFinish
=
async
(
values
)
=>
{
setLoading
(
true
);
let
data
=
null
;
if
(
parentId
)
{
data
=
await
chapterAdd
({
pid
:
parentId
===
-
1
?
0
:
parentId
,
book_id
:
bookId
,
name
:
values
.
title
,
});
}
else
{
data
=
await
chapterEdit
({
id
:
editKey
,
name
:
values
.
title
});
}
await
dispatch
(
setTreeChapter
({
saveBookId
:
bookId
,
saveChapterId
:
data
.
id
}));
if
(
data
)
{
if
(
parentId
)
{
await
sectionAdd
({
book_id
:
bookId
,
chapter_id
:
data
.
id
,
content
:
""
,
word_count
:
0
});
}
setEditKey
(
false
);
setEditValue
(
null
);
await
getChapterTreeList
();
if
(
parentId
)
{
let
temp
=
[...
expandedKeys
,
parentId
];
setExpandedKeys
(
temp
);
await
newChapterChange
(
data
.
id
,
values
.
title
);
}
}
setLoading
(
false
);
};
return
(
<
Form
layout=
"vertical"
form=
{
form
}
onFinish=
{
onFinish
}
initialValues=
{
initValues
}
>
<
Form
.
Item
label=
"章节标题"
name=
"title"
rules=
{
[{
required
:
true
,
message
:
"请输入章节标题"
}]
}
extra=
"最多30个字符"
>
<
Input
ref=
{
inputRef
}
autoFocus
maxLength=
{
30
}
placeholder=
""
allowClear
/>
</
Form
.
Item
>
<
Form
.
Item
className=
"editor-form-buttons"
wrapperCol=
{
{
offset
:
9
,
span
:
16
}
}
>
<
Space
size=
{
20
}
>
<
Button
type=
"default"
disabled=
{
loading
}
onClick=
{
()
=>
setEditKey
(
false
)
}
>
取消
</
Button
>
<
Button
type=
"primary"
loading=
{
loading
}
htmlType=
"submit"
>
确认
</
Button
>
</
Space
>
</
Form
.
Item
>
</
Form
>
);
};
export
default
EditChapterTitle
;
export
{
default
}
from
"@/modules/books/management/chapters/components/form-chapter-title"
;
apps/admin/src/pages/books/section/index.jsx
浏览文件 @
77cf6ebc
import
{
useState
,
useEffect
,
useRef
,
useCallback
}
from
"react"
;
import
{
Button
,
Row
,
Col
,
Descriptions
,
Tree
,
Dropdown
,
Modal
,
Spin
,
App
}
from
"antd"
;
import
{
EllipsisOutlined
,
DiffOutlined
,
MenuFoldOutlined
,
MenuUnfoldOutlined
,
}
from
"@ant-design/icons"
;
import
WangEditorCustomer
from
"@/common/wangeditor-customer"
;
import
{
useLocation
,
useNavigate
}
from
"react-router"
;
import
{
useDispatch
,
useSelector
}
from
"react-redux"
;
import
{
setAutosaveTime
}
from
"@/store/modules/editor"
;
import
{
setTreeChapter
}
from
"@/store/modules/user"
;
import
EditChapterTitle
from
"./components/form-chapter-title"
;
import
EditChapterEditors
from
"./components/EditChapterEditors"
;
import
BindKnowledgeGraph
from
"./components/BindKnowledgeGraph"
;
import
{
get
}
from
"lodash-es"
;
import
md5
from
"js-md5"
;
import
{
convertToAntdTreeData
,
findTreeElementByKey
,
findFirstNotHasChildren
,
findParentLevelOne
,
findTreeToIndex
,
findNodeById
,
}
from
"@/utils/common"
;
import
{
getAllList
,
getInfoByChapterId
,
sectionEdit
,
chapterDel
,
dragOrder
}
from
"./request"
;
import
"./index.less"
;
const
Examine
=
()
=>
{
const
{
modal
}
=
App
.
useApp
();
const
location
=
useLocation
();
const
id
=
get
(
location
,
"state.id"
,
""
);
const
isAdminOrCreator
=
get
(
location
,
"state.isAdminOrCreator"
,
false
);
const
{
treeChapter
,
userInfo
}
=
useSelector
((
state
)
=>
state
.
user
);
const
navigate
=
useNavigate
();
const
dispatch
=
useDispatch
();
const
[
gData
,
setGData
]
=
useState
([]);
const
[
chapterId
,
setChapterId
]
=
useState
(
0
);
const
[
bookId
,
setBookId
]
=
useState
(
0
);
// 树节点设置
const
[
expandedKeys
,
setExpandedKeys
]
=
useState
([]);
const
[
checkedKeys
,
setCheckedKeys
]
=
useState
([]);
const
[
nowTitle
,
setNowTitle
]
=
useState
(
""
);
// 编辑操作
const
[
isCollapse
,
setisCollapse
]
=
useState
(
false
);
const
[
sidebarWidth
,
setSidebarWidth
]
=
useState
(
280
);
const
isResizing
=
useRef
(
false
);
const
sidebarRef
=
useRef
(
null
);
const
[
editKey
,
setEditKey
]
=
useState
(
null
);
const
[
parentId
,
setParentId
]
=
useState
(
null
);
const
[
editValue
,
setEditValue
]
=
useState
(
""
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
contentMd5
,
setContentMd5
]
=
useState
(
""
);
// 设置编者
const
[
currentChapter
,
setCurrentChapter
]
=
useState
(
null
);
const
[
editChapterEditorsIsOpen
,
setEditChapterEditorsIsOpen
]
=
useState
(
false
);
const
onChapterEditorsChange
=
()
=>
{
setEditChapterEditorsIsOpen
(
false
);
};
// 关联知识图谱
const
[
bindKnowledgeGraphIsOpen
,
setBindKnowledgeGraphIsOpen
]
=
useState
(
false
);
// 拖拽改变侧边栏宽度
const
handleMouseDown
=
useCallback
(
(
e
)
=>
{
e
.
preventDefault
();
isResizing
.
current
=
true
;
document
.
body
.
style
.
cursor
=
"col-resize"
;
document
.
body
.
style
.
userSelect
=
"none"
;
const
startX
=
e
.
clientX
;
const
startWidth
=
sidebarWidth
;
const
handleMouseMove
=
(
e
)
=>
{
if
(
!
isResizing
.
current
)
return
;
const
delta
=
e
.
clientX
-
startX
;
const
newWidth
=
Math
.
min
(
Math
.
max
(
startWidth
+
delta
,
200
),
600
);
setSidebarWidth
(
newWidth
);
};
const
handleMouseUp
=
()
=>
{
isResizing
.
current
=
false
;
document
.
body
.
style
.
cursor
=
""
;
document
.
body
.
style
.
userSelect
=
""
;
document
.
removeEventListener
(
"mousemove"
,
handleMouseMove
);
document
.
removeEventListener
(
"mouseup"
,
handleMouseUp
);
};
document
.
addEventListener
(
"mousemove"
,
handleMouseMove
);
document
.
addEventListener
(
"mouseup"
,
handleMouseUp
);
},
[
sidebarWidth
],
);
// 编辑器内容
const
editorRef
=
useRef
();
const
saveInterRef
=
useRef
();
const
[
html
,
setHtml
]
=
useState
(
""
);
const
[
contentId
,
setContentId
]
=
useState
(
false
);
// 获取目录结构
const
getChapterTreeList
=
async
()
=>
{
setLoading
(
true
);
const
{
data
=
[],
code
}
=
await
getAllList
({
book_id
:
id
});
if
(
code
!==
200
)
{
setLoading
(
false
);
navigate
(
"/books/management/list"
,
{
replace
:
true
});
return
;
}
if
(
data
.
length
===
0
)
{
setLoading
(
false
);
return
;
}
const
uniqueBookList
=
[...
new
Set
(
data
.
map
((
item
)
=>
item
.
book_id
))][
0
];
setBookId
(
uniqueBookList
);
const
arr
=
convertToAntdTreeData
(
data
,
"name"
);
setGData
(
arr
);
const
allKeys
=
getAllNodeKeys
(
arr
);
setExpandedKeys
(
allKeys
);
const
first
=
findFirstNotHasChildren
(
arr
);
setNowTitle
(
first
.
title
);
if
(
treeChapter
&&
Object
.
entries
(
treeChapter
).
length
>
0
)
{
if
(
parseInt
(
treeChapter
.
saveBookId
)
===
parseInt
(
id
))
{
const
childInKey
=
findTreeElementByKey
(
arr
,
"key"
,
treeChapter
.
saveChapterId
);
if
(
childInKey
)
{
setChapterId
(
childInKey
.
key
);
setCheckedKeys
([
childInKey
.
key
]);
setNowTitle
(
childInKey
.
title
);
}
else
{
setChapterId
(
first
.
key
);
setCheckedKeys
([
first
.
key
]);
setNowTitle
(
first
.
title
);
}
}
else
{
setChapterId
(
first
.
key
);
setCheckedKeys
([
first
.
key
]);
setNowTitle
(
first
.
title
);
await
dispatch
(
setTreeChapter
({
saveBookId
:
id
,
saveChapterId
:
first
.
key
}));
}
}
else
{
setChapterId
(
first
.
key
);
setCheckedKeys
([
first
.
key
]);
setNowTitle
(
first
.
title
);
}
setLoading
(
false
);
};
const
newChapterChange
=
(
id
,
title
)
=>
{
setChapterId
(
parseInt
(
id
));
setCheckedKeys
([
parseInt
(
id
)]);
setNowTitle
(
title
);
};
// 递归函数,获取所有节点的key
const
getAllNodeKeys
=
(
nodes
)
=>
{
let
keys
=
[];
nodes
.
forEach
((
node
)
=>
{
keys
.
push
(
node
.
key
);
if
(
node
.
children
&&
node
.
children
.
length
>
0
)
{
keys
=
keys
.
concat
(
getAllNodeKeys
(
node
.
children
));
}
});
return
keys
;
};
const
[
disabled
,
setDisabled
]
=
useState
(
false
);
// 获取内容
const
getChapterVal
=
async
()
=>
{
setLoading
(
true
);
const
data
=
await
getInfoByChapterId
({
chapter_id
:
chapterId
});
setDisabled
(
!
data
.
selected_editor_ids
.
includes
(
userInfo
.
id
)
&&
!
isAdminOrCreator
);
dispatch
(
setTreeChapter
({
saveBookId
:
id
,
saveChapterId
:
chapterId
,
...
data
}));
const
{
content
,
id
:
cId
}
=
data
;
if
(
content
)
{
setContentMd5
(
md5
(
content
));
}
setHtml
(
content
);
setContentId
(
cId
);
if
(
editorRef
.
current
&&
editorRef
.
current
.
editor
)
{
editorRef
.
current
.
editor
.
setHtml
(
content
);
}
setLoading
(
false
);
};
const
onExpand
=
(
expandedKeys
)
=>
{
setExpandedKeys
(
expandedKeys
);
};
const
handleSelect
=
async
(
selectedKeys
,
info
)
=>
{
if
(
!
info
.
node
)
return
;
const
{
key
,
title
}
=
info
.
node
;
// 保持选中状态,防止重复点击导致选中状态消失
setCheckedKeys
([
key
]);
// 如果点击的是当前已经选中的章节,且没有发生章节切换,则直接返回
if
(
key
===
chapterId
)
{
return
;
}
setLoading
(
true
);
// 切换章节前清理保存定时器并执行一次保存
clearInterval
(
saveInterRef
.
current
);
await
saveContent
();
// 清理旧内容
if
(
editorRef
.
current
&&
editorRef
.
current
.
editor
)
{
editorRef
.
current
.
editor
.
clear
();
}
// 设置新章节 ID,触发 useEffect 加载数据
setChapterId
(
key
);
setNowTitle
(
title
);
setLoading
(
false
);
};
useEffect
(()
=>
{
getChapterTreeList
();
},
[]);
useEffect
(()
=>
{
if
(
chapterId
)
{
getChapterVal
();
}
},
[
chapterId
]);
// 保存方法
const
saveContent
=
async
()
=>
{
if
((
contentId
,
editorRef
.
current
,
editorRef
.
current
.
editor
,
!
loading
))
{
const
contentHtml
=
editorRef
.
current
.
editor
.
getHtml
();
const
newMd5
=
md5
(
contentHtml
);
clearInterval
(
saveInterRef
.
current
);
const
word_count
=
editorRef
.
current
.
editor
.
getText
().
length
;
// 保存
const
data
=
await
sectionEdit
({
id
:
contentId
,
content
:
contentHtml
,
word_count
,
});
if
(
data
)
{
if
((
data
.
code
&&
data
.
code
===
3000
)
||
data
.
code
===
"3000"
)
{
// navigate('/books/management/list')
return
;
}
else
{
setContentMd5
(
newMd5
);
dispatch
(
setAutosaveTime
(
Date
.
now
()));
}
}
}
};
useEffect
(()
=>
{
if
(
!
loading
&&
contentId
)
{
clearInterval
(
saveInterRef
.
current
);
}
else
{
clearInterval
(
saveInterRef
.
current
);
}
},
[
contentId
,
loading
,
contentMd5
,
html
]);
const
addChapterParent
=
()
=>
{
setEditKey
(
-
1
);
setEditValue
(
""
);
setParentId
(
-
1
);
};
const
delChapter
=
async
(
node
)
=>
{
const
childInKey
=
findParentLevelOne
(
gData
,
"key"
,
node
.
key
);
let
current
=
null
;
if
(
childInKey
&&
childInKey
.
children
&&
childInKey
.
children
.
length
>
0
)
{
const
index
=
findTreeToIndex
(
childInKey
.
children
,
"key"
,
node
.
key
);
if
(
childInKey
.
children
.
length
===
1
)
{
current
=
childInKey
;
}
else
{
// 前面还有
if
(
index
<=
childInKey
.
children
.
length
)
{
if
(
index
===
0
)
{
current
=
childInKey
.
children
[
index
+
1
];
}
else
{
current
=
childInKey
.
children
[
index
-
1
];
}
}
}
}
else
{
const
index
=
findTreeToIndex
(
gData
,
"key"
,
node
.
key
);
let
child
=
null
;
if
(
index
===
0
)
{
child
=
gData
[
index
+
1
];
}
else
{
child
=
gData
[
index
-
1
];
}
if
(
child
&&
child
.
children
&&
child
.
children
.
length
>
0
)
{
current
=
child
.
children
[
0
];
}
else
{
current
=
child
;
}
}
const
data
=
await
chapterDel
({
id
:
node
.
key
});
if
(
data
)
{
await
dispatch
(
setTreeChapter
({
saveBookId
:
id
,
saveChapterId
:
current
?
current
.
key
:
null
}),
);
await
getChapterTreeList
();
}
};
useEffect
(()
=>
{
if
(
gData
&&
gData
.
length
>
0
&&
treeChapter
&&
Object
.
entries
(
treeChapter
).
length
>
0
)
{
if
(
parseInt
(
treeChapter
.
saveBookId
)
===
parseInt
(
id
))
{
const
childInKey
=
findTreeElementByKey
(
gData
,
"key"
,
treeChapter
.
saveChapterId
);
if
(
childInKey
&&
Object
.
entries
(
childInKey
).
length
>
0
)
{
setChapterId
(
parseInt
(
treeChapter
.
saveChapterId
));
setCheckedKeys
([
parseInt
(
treeChapter
.
saveChapterId
)]);
setNowTitle
(
childInKey
.
title
);
}
}
}
},
[
gData
,
treeChapter
]);
let
chapterMenuItems
=
[
{
key
:
"1"
,
label
:
"展开全部"
},
{
key
:
"2"
,
label
:
"添加子节"
},
{
key
:
"7"
,
label
:
"关联知识图谱"
},
{
key
:
"5"
,
label
:
"设置编者"
},
{
key
:
"6"
,
label
:
"一键AI"
,
children
:
[
{
key
:
"61"
,
label
:
"一键AI创作课件"
,
disabled
:
true
},
{
key
:
"62"
,
label
:
"一键AI创作教案"
,
disabled
:
true
},
],
},
{
key
:
"3"
,
label
:
"编辑"
},
{
key
:
"4"
,
label
:
"删除"
},
];
if
(
!
isAdminOrCreator
)
{
chapterMenuItems
=
[{
key
:
"1"
,
label
:
"展开全部"
}];
}
const
handleMenuClick
=
async
(
e
,
node
)
=>
{
e
.
domEvent
.
stopPropagation
();
setCurrentChapter
(
node
);
if
(
parseInt
(
e
.
key
)
===
1
)
{
// 展开全部
const
ids
=
findNodeById
([
node
],
"key"
,
node
.
key
);
const
expandedKeysTemp
=
[...
expandedKeys
,
node
.
key
,
...
ids
];
setExpandedKeys
(
expandedKeysTemp
);
}
else
if
(
parseInt
(
e
.
key
)
===
2
)
{
// 添加子节
setEditKey
(
-
1
);
setEditValue
(
""
);
setParentId
(
node
.
key
);
}
else
if
(
parseInt
(
e
.
key
)
===
3
)
{
// 编辑
setEditKey
(
node
.
key
);
setEditValue
(
node
.
title
);
setParentId
(
false
);
}
else
if
(
parseInt
(
e
.
key
)
===
4
)
{
await
modal
.
confirm
({
title
:
"删除确认"
,
content
:
`确认删除子节【
${
node
.
title
}
】?`
,
onOk
()
{
delChapter
(
node
);
},
});
}
else
if
(
e
.
key
==
5
)
{
setEditChapterEditorsIsOpen
(
true
);
}
else
if
(
e
.
key
==
7
)
{
setBindKnowledgeGraphIsOpen
(
true
);
}
};
// 章节名称
const
chapterTitleRender
=
(
node
)
=>
{
return
(
<
div
className=
"chapter-tree-item"
>
<
p
className=
"title"
>
{
node
.
title
}
</
p
>
<
Dropdown
trigger=
{
[
"click"
]
}
menu=
{
{
items
:
chapterMenuItems
,
onClick
:
(
e
)
=>
handleMenuClick
(
e
,
node
)
}
}
>
<
EllipsisOutlined
onClick=
{
(
e
)
=>
e
.
stopPropagation
()
}
/>
</
Dropdown
>
</
div
>
);
};
const
onDrop
=
async
(
info
)
=>
{
const
dropToGap
=
info
.
dropToGap
;
// true 为同级或最大级 false 为子级
const
nowDropNode
=
info
.
dragNode
.
key
;
const
sideDropNode
=
info
.
node
.
key
;
let
data
=
null
;
if
(
dropToGap
)
{
// 同级
const
dragOverGapBottom
=
info
.
node
.
dragOverGapBottom
;
const
dragOver
=
info
.
node
.
dragOver
;
const
dragOverGapTop
=
info
.
node
.
dragOverGapTop
;
if
(
!
dragOverGapTop
&&
!
dragOver
&&
!
dragOverGapBottom
)
{
// 拖到最底部
// 拖拽到最顶层
data
=
await
dragOrder
({
book_id
:
id
,
drag_node
:
nowDropNode
,
position_node
:
sideDropNode
,
drop_type
:
"after"
,
});
}
else
{
if
(
dragOverGapTop
)
{
// 拖拽到最顶层
data
=
await
dragOrder
({
book_id
:
id
,
drag_node
:
nowDropNode
,
position_node
:
sideDropNode
,
drop_type
:
"before"
,
});
}
else
if
(
dragOverGapBottom
)
{
data
=
await
dragOrder
({
book_id
:
id
,
drag_node
:
nowDropNode
,
position_node
:
sideDropNode
,
drop_type
:
"after"
,
});
}
}
}
else
{
// 子级
data
=
await
dragOrder
({
book_id
:
id
,
drag_node
:
nowDropNode
,
position_node
:
sideDropNode
,
drop_type
:
"inner"
,
});
}
if
(
data
)
{
await
getChapterTreeList
();
}
};
return
(
<
div
className=
"examine"
>
<
div
className=
"content-box"
>
<
div
className=
"book-content-row"
>
<
div
ref=
{
sidebarRef
}
className=
"book-content-tree"
style=
{
{
width
:
isCollapse
?
50
:
sidebarWidth
,
minWidth
:
isCollapse
?
50
:
200
,
flexShrink
:
0
,
}
}
>
<
div
className=
"border"
>
{
!
isCollapse
?
(
<>
<
Descriptions
layout=
"vertical"
rootClassName=
"section-left-top"
bordered
items=
{
[
{
key
:
"1"
,
label
:
(
<
Row
gutter=
{
5
}
justify=
{
"space-between"
}
style=
{
{
alignItems
:
"center"
}
}
>
<
Col
>
章节目录
</
Col
>
<
Col
>
{
isAdminOrCreator
&&
(
<
Button
type=
"text"
icon=
{
<
DiffOutlined
/>
}
onClick=
{
addChapterParent
}
></
Button
>
)
}
<
Button
type=
"text"
icon=
{
<
MenuFoldOutlined
/>
}
onClick=
{
()
=>
setisCollapse
(
true
)
}
></
Button
>
</
Col
>
</
Row
>
),
},
]
}
/>
{
gData
&&
gData
.
length
>
0
&&
(
<
Tree
className=
"draggable-tree"
onSelect=
{
handleSelect
}
defaultExpandAll
defaultExpandedKeys=
{
expandedKeys
}
expandedKeys=
{
expandedKeys
}
defaultSelectedKeys=
{
checkedKeys
}
selectedKeys=
{
checkedKeys
}
onDrop=
{
onDrop
}
draggable
blockNode
disabled=
{
loading
}
treeData=
{
gData
}
onExpand=
{
onExpand
}
titleRender=
{
(
nodeData
)
=>
chapterTitleRender
(
nodeData
)
}
/>
)
}
</>
)
:
(
<
Descriptions
layout=
"vertical"
bordered
items=
{
[
{
key
:
"1"
,
label
:
(
<
Row
gutter=
{
5
}
justify=
{
"space-between"
}
style=
{
{
alignItems
:
"center"
}
}
>
<
Col
>
<
Button
type=
"text"
icon=
{
<
MenuUnfoldOutlined
/>
}
onClick=
{
()
=>
setisCollapse
(
false
)
}
></
Button
>
</
Col
>
</
Row
>
),
},
]
}
/>
)
}
</
div
>
</
div
>
{
!
isCollapse
&&
<
div
className=
"resize-handle"
onMouseDown=
{
handleMouseDown
}
/>
}
<
div
className=
"book-content-editor"
style=
{
{
flex
:
1
,
minWidth
:
0
}
}
>
<
div
className=
"editor-right"
>
<
Spin
spinning=
{
loading
}
>
<
WangEditorCustomer
ref=
{
editorRef
}
chapterId=
{
chapterId
}
bookId=
{
id
}
contentId=
{
contentId
}
setHtml=
{
setHtml
}
html=
{
html
}
saveContent=
{
saveContent
}
gData=
{
gData
}
nowTitle=
{
nowTitle
}
disabled=
{
disabled
}
/>
</
Spin
>
</
div
>
</
div
>
</
div
>
</
div
>
<
Modal
open=
{
editKey
}
footer=
{
null
}
centered
destroyOnClose
title=
{
editKey
>
0
?
"修改章节"
:
"新增章节"
}
maskClosable=
{
false
}
mask=
{
false
}
closeIcon=
{
false
}
// 添加这一行以隐藏关闭按钮
onCancel=
{
()
=>
setEditKey
(
false
)
}
classNames=
{
{
wrapper
:
"chapter-title-modal"
,
}
}
>
<
EditChapterTitle
setEditKey=
{
setEditKey
}
editValue=
{
editValue
}
editKey=
{
editKey
}
getChapterTreeList=
{
getChapterTreeList
}
setEditValue=
{
setEditValue
}
parentId=
{
parentId
}
bookId=
{
id
}
expandedKeys=
{
expandedKeys
}
setExpandedKeys=
{
setExpandedKeys
}
editorLoading=
{
loading
}
newChapterChange=
{
newChapterChange
}
/>
</
Modal
>
{
/* 设置章节编写者 */
}
{
currentChapter
&&
editChapterEditorsIsOpen
&&
(
<
EditChapterEditors
open=
{
editChapterEditorsIsOpen
}
chapter=
{
currentChapter
}
onCancel=
{
()
=>
setEditChapterEditorsIsOpen
(
false
)
}
onChange=
{
onChapterEditorsChange
}
></
EditChapterEditors
>
)
}
{
/* 关联知识图谱 */
}
{
currentChapter
&&
bindKnowledgeGraphIsOpen
&&
(
<
BindKnowledgeGraph
open=
{
bindKnowledgeGraphIsOpen
}
chapter=
{
currentChapter
}
onCancel=
{
()
=>
setBindKnowledgeGraphIsOpen
(
false
)
}
onOk=
{
()
=>
setBindKnowledgeGraphIsOpen
(
false
)
}
></
BindKnowledgeGraph
>
)
}
</
div
>
);
};
export
default
Examine
;
export
{
default
}
from
"@/modules/books/management/components/ManagementChapter"
;
apps/admin/src/pages/books/section/index.less
浏览文件 @
77cf6ebc
.examine {
height: 100%;
.content-box {
height: 100%;
box-sizing: border-box;
overflow: hidden;
.book-content-row {
height: 100%;
overflow: hidden;
display: flex;
gap: 0;
.book-content-tree {
height: 100%;
overflow: hidden;
transition: width 0s;
}
.book-content-editor {
height: 100%;
overflow: hidden;
}
}
.resize-handle {
width: 6px;
cursor: col-resize;
background: transparent;
position: relative;
flex-shrink: 0;
z-index: 10;
transition: background 0.2s;
&:hover,
&:active {
background: #1890ff;
border-radius: 3px;
}
&::after {
content: "";
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 2px;
height: 30px;
background: #d9d9d9;
border-radius: 1px;
transition: background 0.2s;
}
&:hover::after,
&:active::after {
background: #fff;
}
}
.ant-descriptions-item-label {
padding: 10px !important;
}
.ant-descriptions-view {
border: none;
}
.border {
border: 1px solid #e5e5e5;
height: 100%;
display: flex;
overflow-x: hidden;
overflow-y: auto;
flex-direction: column;
border-radius: 6px;
.section-left-top {
height: 85px;
}
.ant-descriptions.ant-descriptions-bordered > .ant-descriptions-view .ant-descriptions-row {
border-bottom: 1px solid #e5e5e5;
&:nth-of-type(2) {
display: none;
}
}
}
.editor-right {
height: 100%;
.ant-spin-nested-loading,
.ant-spin-container {
height: 100%;
}
}
}
.draggable-tree {
flex: 1;
height: calc(100vh - 285px);
overflow-y: auto;
overflow-x: hidden;
width: 98%;
&.ant-tree .ant-tree-node-content-wrapper {
width: 100%;
}
.ant-tree-switcher-noop {
// display: none;
width: 10px;
}
&.ant-tree .ant-tree-node-content-wrapper {
overflow: hidden;
}
}
.chapter-tree-item {
display: flex;
justify-content: space-between;
overflow: hidden;
.title {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
padding-right: 10px;
max-height: 24px;
line-height: 24px;
white-space: nowrap;
}
}
}
.chapter-title-modal {
.ant-modal {
.ant-modal-close {
inset-inline-end: 17px;
top: 16px;
}
.ant-modal-content {
padding: 15px 18px;
}
.ant-divider {
margin: 15px 0;
}
}
}
@import "@/modules/books/management/chapters/index.less";
apps/admin/src/pages/books/section/request.js
浏览文件 @
77cf6ebc
import
axios
from
"@/utils/request.js"
;
import
qs
from
"qs"
;
import
{
get
}
from
"lodash-es"
;
import
{
message
}
from
"antd"
;
// 用户信息
const
getUserInfo
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/user/user/getUserInfo"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 获取角色已选权限
const
getPowerByRoleId
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/user/role/getPowerByRoleId"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 章节列表
const
getAllList
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/chapter/getAllList"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
{
code
,
data
,
};
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 添加章节
const
chapterAdd
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/chapter/add"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
// message.success('添加成功');
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 章节详情
const
getInfoById
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/chapter/getInfoById"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 修改章节
const
chapterEdit
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/chapter/edit"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
message
.
success
(
msg
);
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 删除章节
const
chapterDel
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/chapter/del"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
message
.
success
(
msg
);
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 拖拽章节排序
const
dragOrder
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/chapter/dragOrder"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
message
.
success
(
msg
);
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 获取章节内容
const
getInfoByChapterId
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/section/getInfoByChapterId"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 添加章节内容
const
sectionAdd
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/section/add"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
message
.
success
(
msg
);
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 修改章节内容
const
sectionEdit
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/section/edit"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
// message.success(msg);
return
true
;
}
else
{
message
.
error
(
msg
);
return
{
code
,
msg
,
data
};
}
}
catch
(
error
)
{
return
false
;
}
};
// 获取章节的题库列表
const
getQuestionList
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/chapter/question/getQuestionList"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 移除课后题
const
questionDel
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/chapter/question/del"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 添加课后题
const
questionAdd
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/chapter/question/add"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 发布审核
const
release
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/release"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 模板列表
const
templateGetList
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/template/getList"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 模板导入
const
templateImport
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/template/import"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 书籍导入
const
teacherImport
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/import"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 保存历史记录
const
getRecordList
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/section/getRecordList"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
export
{
getAllList
,
chapterAdd
,
getInfoById
,
chapterEdit
,
chapterDel
,
dragOrder
,
getInfoByChapterId
,
sectionAdd
,
sectionEdit
,
getQuestionList
,
questionDel
,
questionAdd
,
release
,
templateGetList
,
templateImport
,
teacherImport
,
getRecordList
,
getPowerByRoleId
,
getUserInfo
,
};
export
*
from
"@/modules/books/management/chapters/request"
;
apps/admin/src/pages/jurisdiction/index.jsx
浏览文件 @
77cf6ebc
import
React
from
"react"
;
import
{
Outlet
}
from
"react-router"
;
const
Jurisdiction
=
()
=>
{
return
(
<
div
>
<
Outlet
/>
</
div
>
);
};
export
default
Jurisdiction
;
export
{
default
}
from
'@/modules/access/Layout'
apps/admin/src/pages/member/index.jsx
浏览文件 @
77cf6ebc
import
React
from
"react"
;
import
{
Outlet
}
from
"react-router"
;
const
UserList
=
()
=>
{
return
(
<
div
>
<
Outlet
/>
</
div
>
);
};
export
default
UserList
;
export
{
default
}
from
'@/modules/members/Layout'
apps/admin/src/pages/setting/help/addedit/requet.js
浏览文件 @
77cf6ebc
import
axios
from
"@/utils/request.js"
;
import
qs
from
"qs"
;
import
{
message
}
from
"antd"
;
import
{
get
}
from
"lodash-es"
;
// oss
const
getAliOSSSTSToken
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/common/OssUpload/getUploadToken"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"获取OSS令牌失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
export
{
getAliOSSSTSToken
};
export
*
from
"@/modules/settings/help/request"
;
apps/admin/src/pages/setting/index.jsx
浏览文件 @
77cf6ebc
import
React
from
"react"
;
import
{
Outlet
}
from
"react-router"
;
const
Role
=
()
=>
{
return
(
<
div
>
<
Outlet
/>
</
div
>
);
};
export
default
Role
;
export
{
default
}
from
"@/modules/settings/SettingsLayout"
;
apps/admin/src/pages/setting/personalized-setting/index.jsx
浏览文件 @
77cf6ebc
import
React
,
{
useState
,
useEffect
,
useRef
}
from
"react"
;
import
{
Form
,
Input
,
Radio
,
Space
,
Button
,
Spin
,
DatePicker
}
from
"antd"
;
import
{
getSystem
,
systemEdit
}
from
"../point-management/request"
;
const
Personalized
=
()
=>
{
const
[
form
]
=
Form
.
useForm
();
const
flag
=
useRef
(
true
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
// 获取信息
const
getInfo
=
async
()
=>
{
console
.
log
(
222
);
setLoading
(
true
);
const
data
=
await
getSystem
();
form
.
setFieldsValue
(
data
);
setLoading
(
false
);
};
// 设置个性化信息
const
handleSystemEdit
=
async
(
obj
)
=>
{
const
bool
=
await
systemEdit
(
obj
);
bool
&&
getInfo
();
};
useEffect
(()
=>
{
getInfo
();
},
[]);
return
(
<
div
style=
{
{
padding
:
20
}
}
>
<
Spin
spinning=
{
loading
}
>
<
Form
form=
{
form
}
onFinish=
{
handleSystemEdit
}
>
<
Form
.
Item
label=
"老师分成百分比(%)"
name=
"teacher_divide"
rules=
{
[{
required
:
true
,
message
:
"老师分成百分比不能为空"
}]
}
extra=
"老师佣金计算(预计总收入),编写书籍售卖总金额+分成比例"
>
<
Input
autoComplete=
"off"
style=
{
{
width
:
260
}
}
placeholder=
"请输入老师分成百分比"
></
Input
>
</
Form
.
Item
>
<
Form
.
Item
label=
"用户等级有效期(月)"
name=
"user_grade_validity"
rules=
{
[{
required
:
true
,
message
:
"请输入用户等级有效期(月)"
}]
}
extra=
"用于计算用户等级,根据当前时间之前有效期内的消费金额总和来计算"
>
<
Input
autoComplete=
"off"
style=
{
{
width
:
260
}
}
placeholder=
"请输入用户等级有效期"
></
Input
>
</
Form
.
Item
>
<
Form
.
Item
name=
"comments_need_approval"
label=
"评论是否需要审核"
rules=
{
[{
required
:
true
,
message
:
"请选择评论是否需要审核"
}]
}
>
<
Radio
.
Group
>
<
Radio
value=
{
"1"
}
>
是
</
Radio
>
<
Radio
value=
{
"0"
}
>
否
</
Radio
>
</
Radio
.
Group
>
</
Form
.
Item
>
<
Form
.
Item
wrapperCol=
{
{
offset
:
2
}
}
>
<
Space
>
<
Button
type=
"primary"
ghost
onClick=
{
()
=>
{
form
.
setFieldsValue
({
teacher_divide
:
""
,
user_grade_validity
:
""
,
comments_need_approval
:
""
,
});
}
}
>
重置
</
Button
>
<
Button
type=
"primary"
loading=
{
loading
}
htmlType=
"submit"
>
确定
</
Button
>
</
Space
>
</
Form
.
Item
>
</
Form
>
</
Spin
>
</
div
>
);
};
export
default
Personalized
;
export
{
default
}
from
"@/modules/settings/personalized/views/PersonalizedSettingsView"
;
apps/admin/src/pages/setting/point-management/index.jsx
浏览文件 @
77cf6ebc
import
React
,
{
useState
,
useRef
,
useEffect
}
from
"react"
;
import
{
Button
,
Form
,
Input
,
Switch
,
Space
,
Drawer
,
Spin
}
from
"antd"
;
import
{
getIntegral
,
integralSet
,
integralEdit
,
integralAdd
,
getSystem
,
systemEdit
,
getList
,
}
from
"./request"
;
import
{
useSelector
}
from
"react-redux"
;
const
{
TextArea
}
=
Input
;
const
Point
=
()
=>
{
const
flag
=
useRef
(
true
);
const
[
form
]
=
Form
.
useForm
();
const
[
form2
]
=
Form
.
useForm
();
const
[
initialValues
]
=
useState
({
reward_points_ratio
:
""
,
one_point_deduction_amount
:
""
,
integral_use_limit
:
""
,
});
const
[
rechargeModal
,
setRechargeModal
]
=
useState
(
false
);
const
[
integralSwitch
,
setIntegralSwitch
]
=
useState
(
false
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
// 获取操作权限
const
{
operationPermissionsList
}
=
useSelector
((
state
)
=>
state
.
user
);
// 获取信息
const
geySettingInfo
=
async
()
=>
{
setLoading
(
true
);
const
data
=
await
getIntegral
();
const
{
integral_switch
}
=
data
;
if
(
integral_switch
==
"0"
)
{
setIntegralSwitch
(
false
);
}
else
{
setIntegralSwitch
(
true
);
}
form
.
setFieldsValue
(
data
);
setLoading
(
false
);
};
// 充值积分
const
submitForm
=
async
(
obj
)
=>
{
const
bool
=
await
integralAdd
(
obj
);
bool
&&
setRechargeModal
(
false
);
};
// 修改设置
const
editInfo
=
async
(
obj
)
=>
{
let
copyObj
=
{
...
obj
};
delete
copyObj
.
integral_switch
;
const
bool
=
await
integralEdit
(
obj
);
bool
&&
geySettingInfo
();
};
// 修改开关
const
changeIntegralSwitch
=
async
(
val
)
=>
{
const
integral_switch
=
val
?
"1"
:
"0"
;
const
bool
=
await
integralSet
({
integral_switch
});
bool
&&
geySettingInfo
();
if
(
!
val
)
{
setRechargeModal
(
false
);
}
};
useEffect
(()
=>
{
// if(!flag.current){
geySettingInfo
();
// }else{
// flag.current=false
// }
},
[]);
const
responsiveFormItemLayout
=
{
labelCol
:
{
xxl
:
2
,
xl
:
3
,
lg
:
4
,
md
:
5
,
sm
:
6
,
xs
:
8
},
wrapperCol
:
{
xs
:
24
,
sm
:
18
},
};
return
(
<
div
style=
{
{
padding
:
20
}
}
>
<
Spin
spinning=
{
loading
}
>
<
Form
form=
{
form
}
{
...
responsiveFormItemLayout
}
initialValues=
{
initialValues
}
onFinish=
{
editInfo
}
>
<
Form
.
Item
label=
"积分开关"
>
<
div
>
<
Switch
checked=
{
integralSwitch
}
onChange=
{
changeIntegralSwitch
}
disabled=
{
!
operationPermissionsList
.
includes
(
"/setting/point-management/integralSet"
)
}
></
Switch
>
{
operationPermissionsList
.
includes
(
"/setting/point-management/add"
)
&&
(
<
Button
disabled=
{
!
integralSwitch
}
style=
{
{
marginLeft
:
30
,
opacity
:
integralSwitch
?
"1"
:
"0"
}
}
onClick=
{
()
=>
{
setRechargeModal
(
true
);
}
}
type=
"primary"
ghost
>
积分充值
</
Button
>
)
}
</
div
>
</
Form
.
Item
>
<
Form
.
Item
label=
"赠送积分比例(%)"
name=
"reward_points_ratio"
rules=
{
[
{
required
:
true
,
message
:
"请输入赠送积分比例"
},
{
validator
:
(
_
,
value
)
=>
{
if
(
/^
(?:
100|
[
1-9
]?[
0-9
])
$/
.
test
(
value
)
||
!
value
)
{
return
Promise
.
resolve
();
}
else
{
return
Promise
.
reject
(
"请输入区间0-100的正整数"
);
}
},
},
]
}
>
<
Input
autoComplete=
"off"
style=
{
{
width
:
340
}
}
placeholder=
"请输入赠送积分比例"
></
Input
>
</
Form
.
Item
>
<
Form
.
Item
label=
"1积分抵扣金额(元)"
name=
"one_point_deduction_amount"
rules=
{
[
{
required
:
true
,
message
:
"请输入1积分抵扣金额"
},
{
validator
:
(
_
,
value
)
=>
{
if
(
/^
(?:
100
(?:\.
0{1,2}
)?
|
\d
{1,2}
(?:\.\d
{1,2}
)?)
$/
.
test
(
value
)
||
!
value
)
{
return
Promise
.
resolve
();
}
else
{
return
Promise
.
reject
(
"请输入区间0-100的数字,可以是小数,支持俩位小数"
);
}
},
},
]
}
>
<
Input
autoComplete=
"off"
style=
{
{
width
:
340
}
}
placeholder=
"请输入1积分抵扣金额"
></
Input
>
</
Form
.
Item
>
<
Form
.
Item
label=
"积分使用上限(%)"
name=
"integral_use_limit"
rules=
{
[
{
required
:
true
,
message
:
"请输入积分使用上限"
},
{
validator
:
(
_
,
value
)
=>
{
if
(
/^
(?:
100|
[
1-9
]?[
0-9
])
$/
.
test
(
value
)
||
!
value
)
{
return
Promise
.
resolve
();
}
else
{
return
Promise
.
reject
(
"请输入区间0-100的正整数"
);
}
},
},
]
}
>
<
Input
autoComplete=
"off"
style=
{
{
width
:
340
}
}
placeholder=
"请输入积分使用上限"
></
Input
>
</
Form
.
Item
>
<
Form
.
Item
wrapperCol=
{
{
offset
:
3
,
span
:
16
}
}
>
<
Space
size=
{
20
}
>
{
operationPermissionsList
.
includes
(
"/setting/point-management/integralEdit"
)
&&
(
<
Button
onClick=
{
()
=>
{
form
.
setFieldsValue
(
initialValues
);
}
}
>
重置
</
Button
>
)
}
{
operationPermissionsList
.
includes
(
"/setting/point-management/integralEdit"
)
&&
(
<
Button
type=
"primary"
htmlType=
"submit"
>
确定
</
Button
>
)
}
</
Space
>
</
Form
.
Item
>
</
Form
>
</
Spin
>
<
Drawer
placement=
"right"
onClose=
{
()
=>
setRechargeModal
(
false
)
}
open=
{
rechargeModal
}
mask=
{
false
}
>
<
Form
form=
{
form2
}
onFinish=
{
submitForm
}
labelCol=
{
{
span
:
6
}
}
>
<
Form
.
Item
name=
"phone"
label=
"用户手机号"
rules=
{
[{
required
:
true
,
message
:
"请输入用户手机号"
}]
}
>
<
TextArea
placeholder=
"请输入用户手机号,用逗号隔开,每次最多100个"
autoSize=
{
{
minRows
:
5
,
maxRows
:
8
,
}
}
></
TextArea
>
</
Form
.
Item
>
<
Form
.
Item
name=
"num"
label=
"充值积分"
rules=
{
[
{
required
:
true
,
message
:
"请输入充值积分数量"
},
{
pattern
:
/^
[
1-9
]\d
*$/
,
message
:
"请输入正整数"
},
]
}
>
<
Input
autoComplete=
"off"
placeholder=
"请输入充值积分数量"
></
Input
>
</
Form
.
Item
>
<
Form
.
Item
wrapperCol=
{
{
offset
:
6
,
span
:
16
}
}
style=
{
{
textAlign
:
"center"
}
}
>
<
Space
size=
{
20
}
>
<
Button
onClick=
{
()
=>
setRechargeModal
(
false
)
}
>
取消
</
Button
>
<
Button
type=
"primary"
htmlType=
"submit"
>
提交
</
Button
>
</
Space
>
</
Form
.
Item
>
</
Form
>
</
Drawer
>
</
div
>
);
};
export
default
Point
;
export
{
default
}
from
"@/modules/settings/point-management/views/PointManagementView"
;
apps/admin/src/pages/setting/point-management/request.js
浏览文件 @
77cf6ebc
import
axios
from
"@/utils/request.js"
;
import
qs
from
"qs"
;
import
{
message
}
from
"antd"
;
import
{
get
}
from
"lodash-es"
;
// 获取积分设置信息
const
getIntegral
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/system/config/getIntegral"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
error
;
}
};
// 设置积分开关
const
integralSet
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/system/config/integralSet"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
error
;
}
};
// 修改积分配置
const
integralEdit
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/system/config/integralEdit"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
message
.
success
(
msg
);
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
error
;
}
};
// 充值积分
const
integralAdd
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/members/integral/add"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
3000
)
{
message
.
warning
(
"优惠券已过期"
);
return
true
;
}
// 先检查空手机号的情况,如果满足就跳出后续错误提示
if
(
data
.
err
.
msg
===
" 手机号:"
&&
data
.
suc
.
title
&&
!
data
.
suc
.
title
.
includes
(
"充值成功(0)"
)
)
{
if
(
data
.
suc
.
msg
!==
""
)
{
message
.
success
(
`
${
data
.
suc
.
msg
}
充值成功`
,
10
);
}
// message.error(`空手机号充值失败`, 10);
return
true
;
}
// 接下来处理其他非空错误消息和成功消息
if
(
data
.
suc
.
title
&&
!
data
.
suc
.
title
.
includes
(
"充值成功(0)"
))
{
if
(
data
.
suc
.
msg
!==
""
)
{
message
.
success
(
`
${
data
.
suc
.
msg
}
充值成功`
,
10
);
}
}
if
(
data
.
err
.
title
&&
!
data
.
err
.
title
.
includes
(
"充值失败(0)"
))
{
if
(
data
.
err
.
msg
!==
""
)
{
message
.
error
(
`
${
data
.
err
.
msg
}
充值失败`
,
10
);
}
}
return
true
;
}
catch
(
error
)
{
return
error
;
}
};
// 获取设置信息
const
getSystem
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/system/config/getSystem"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
error
;
}
};
// 修改设置
const
systemEdit
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/system/config/systemEdit"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
message
.
success
(
msg
);
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
error
;
}
};
// 获取意见反馈列表
const
getList
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/system/proposal/getList"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
error
;
}
};
export
{
getIntegral
,
integralSet
,
integralEdit
,
integralAdd
,
getSystem
,
systemEdit
,
getList
};
export
{
getPointSettings
as
getIntegral
,
setPointSwitch
as
integralSet
,
savePointSettings
as
integralEdit
,
rechargePoints
as
integralAdd
,
}
from
"@/modules/settings/point-management/api"
;
export
{
getPersonalizedSettings
as
getSystem
,
savePersonalizedSettings
as
systemEdit
,
}
from
"@/modules/settings/personalized/api"
;
export
{
getFeedbackList
as
getList
}
from
"@/modules/settings/feedback/api"
;
apps/admin/src/pages/student/book/index.jsx
浏览文件 @
77cf6ebc
import
{
useState
,
useEffect
,
useRef
}
from
"react"
;
import
{
Button
,
Row
,
Col
,
Descriptions
,
Tree
,
Dropdown
,
Spin
,
App
}
from
"antd"
;
import
{
EllipsisOutlined
,
DiffOutlined
,
MenuFoldOutlined
,
MenuUnfoldOutlined
,
}
from
"@ant-design/icons"
;
import
WangEditorCustomer
from
"@/common/wangeditor-customer"
;
import
{
useLocation
,
useNavigate
,
useSearchParams
}
from
"react-router"
;
import
{
useDispatch
,
useSelector
}
from
"react-redux"
;
import
{
setAutosaveTime
}
from
"@/store/modules/editor"
;
import
{
setTreeChapter
}
from
"@/store/modules/user"
;
import
{
get
}
from
"lodash-es"
;
import
md5
from
"js-md5"
;
import
{
convertToAntdTreeData
,
findTreeElementByKey
,
findFirstNotHasChildren
,
findParentLevelOne
,
findTreeToIndex
,
findNodeById
,
}
from
"@/utils/common"
;
import
{
getAllList
,
getInfoByChapterId
,
sectionEdit
,
chapterDel
,
dragOrder
}
from
"./request"
;
import
"./index.less"
;
const
Examine
=
()
=>
{
const
{
modal
}
=
App
.
useApp
();
const
[
searchParams
]
=
useSearchParams
();
const
location
=
useLocation
();
const
id
=
searchParams
.
get
(
"book_id"
);
const
isAdminOrCreator
=
get
(
location
,
"state.isAdminOrCreator"
,
false
);
const
{
treeChapter
,
userInfo
}
=
useSelector
((
state
)
=>
state
.
user
);
const
navigate
=
useNavigate
();
const
dispatch
=
useDispatch
();
const
[
gData
,
setGData
]
=
useState
([]);
const
[
chapterId
,
setChapterId
]
=
useState
(
0
);
const
[
bookId
,
setBookId
]
=
useState
(
0
);
// 树节点设置
const
[
expandedKeys
,
setExpandedKeys
]
=
useState
([]);
const
[
checkedKeys
,
setCheckedKeys
]
=
useState
([]);
const
[
nowTitle
,
setNowTitle
]
=
useState
(
""
);
// 编辑操作
const
[
isCollapse
,
setisCollapse
]
=
useState
(
false
);
const
[
editKey
,
setEditKey
]
=
useState
(
null
);
const
[
parentId
,
setParentId
]
=
useState
(
null
);
const
[
editValue
,
setEditValue
]
=
useState
(
""
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
contentMd5
,
setContentMd5
]
=
useState
(
""
);
// 设置编者
const
[
currentChapter
,
setCurrentChapter
]
=
useState
(
null
);
const
[
editChapterEditorsIsOpen
,
setEditChapterEditorsIsOpen
]
=
useState
(
false
);
const
onChapterEditorsChange
=
()
=>
{
setEditChapterEditorsIsOpen
(
false
);
};
// 编辑器内容
const
editorRef
=
useRef
();
const
saveInterRef
=
useRef
();
const
[
html
,
setHtml
]
=
useState
(
""
);
const
[
contentId
,
setContentId
]
=
useState
(
false
);
// 获取目录结构
const
getChapterTreeList
=
async
()
=>
{
setLoading
(
true
);
const
{
data
=
[],
code
}
=
await
getAllList
({
book_id
:
id
});
if
(
code
!==
200
)
{
setLoading
(
false
);
navigate
(
"/books/management/list"
,
{
replace
:
true
});
return
;
}
if
(
data
.
length
===
0
)
{
setLoading
(
false
);
return
;
}
const
uniqueBookList
=
[...
new
Set
(
data
.
map
((
item
)
=>
item
.
book_id
))][
0
];
setBookId
(
uniqueBookList
);
const
arr
=
convertToAntdTreeData
(
data
,
"name"
);
setGData
(
arr
);
const
allKeys
=
getAllNodeKeys
(
arr
);
setExpandedKeys
(
allKeys
);
const
first
=
findFirstNotHasChildren
(
arr
);
setNowTitle
(
first
.
title
);
if
(
treeChapter
&&
Object
.
entries
(
treeChapter
).
length
>
0
)
{
if
(
parseInt
(
treeChapter
.
saveBookId
)
===
parseInt
(
id
))
{
const
childInKey
=
findTreeElementByKey
(
arr
,
"key"
,
searchParams
.
get
(
"chapter_id"
)
||
treeChapter
.
saveChapterId
,
);
if
(
childInKey
)
{
setChapterId
(
childInKey
.
key
);
setCheckedKeys
([
childInKey
.
key
]);
setNowTitle
(
childInKey
.
title
);
}
else
{
setChapterId
(
first
.
key
);
setCheckedKeys
([
first
.
key
]);
setNowTitle
(
first
.
title
);
}
}
else
{
setChapterId
(
first
.
key
);
setCheckedKeys
([
first
.
key
]);
setNowTitle
(
first
.
title
);
await
dispatch
(
setTreeChapter
({
saveBookId
:
id
,
saveChapterId
:
first
.
key
}));
}
}
else
{
setChapterId
(
first
.
key
);
setCheckedKeys
([
first
.
key
]);
setNowTitle
(
first
.
title
);
}
setLoading
(
false
);
};
const
newChapterChange
=
(
id
,
title
)
=>
{
setChapterId
(
parseInt
(
id
));
setCheckedKeys
([
parseInt
(
id
)]);
setNowTitle
(
title
);
};
// 递归函数,获取所有节点的key
const
getAllNodeKeys
=
(
nodes
)
=>
{
let
keys
=
[];
nodes
.
forEach
((
node
)
=>
{
keys
.
push
(
node
.
key
);
if
(
node
.
children
&&
node
.
children
.
length
>
0
)
{
keys
=
keys
.
concat
(
getAllNodeKeys
(
node
.
children
));
}
});
return
keys
;
};
const
[
disabled
,
setDisabled
]
=
useState
(
false
);
// 获取内容
const
getChapterVal
=
async
()
=>
{
setLoading
(
true
);
const
data
=
await
getInfoByChapterId
({
chapter_id
:
chapterId
});
setDisabled
(
!
data
.
selected_editor_ids
.
includes
(
userInfo
.
id
)
&&
!
isAdminOrCreator
);
dispatch
(
setTreeChapter
({
saveBookId
:
id
,
saveChapterId
:
chapterId
,
...
data
}));
const
{
content
,
id
:
cId
}
=
data
;
if
(
content
)
{
setContentMd5
(
md5
(
content
));
}
setHtml
(
content
);
setContentId
(
cId
);
if
(
editorRef
.
current
&&
editorRef
.
current
.
editor
)
{
editorRef
.
current
.
editor
.
setHtml
(
content
);
}
setLoading
(
false
);
};
const
onExpand
=
(
expandedKeys
)
=>
{
setExpandedKeys
(
expandedKeys
);
};
const
handleSelect
=
async
(
checkedKeys
,
info
)
=>
{
const
{
key
}
=
info
.
node
;
const
childInKey
=
findTreeElementByKey
(
gData
,
"key"
,
key
);
if
(
childInKey
.
children
&&
childInKey
.
children
.
length
>
0
)
{
clearInterval
(
saveInterRef
.
current
);
// 进行展开操作
const
tempExpandeds
=
JSON
.
parse
(
JSON
.
stringify
(
expandedKeys
));
let
newExpand
=
""
;
if
(
expandedKeys
.
includes
(
key
))
{
newExpand
=
tempExpandeds
.
filter
((
item
)
=>
parseInt
(
item
)
!==
parseInt
(
key
));
}
else
{
newExpand
=
[...
tempExpandeds
,
key
];
}
setExpandedKeys
(
newExpand
);
setCheckedKeys
([
key
]);
return
;
}
else
{
const
{
key
,
title
}
=
info
.
node
;
if
(
info
.
selected
===
false
)
{
setLoading
(
true
);
setChapterId
(
key
);
setCheckedKeys
([
key
]);
setNowTitle
(
title
);
setLoading
(
false
);
}
else
{
setLoading
(
true
);
clearInterval
(
saveInterRef
.
current
);
await
saveContent
();
if
(
key
!==
chapterId
)
{
editorRef
.
current
.
editor
.
clear
();
}
setLoading
(
true
);
setChapterId
(
checkedKeys
[
0
]);
setCheckedKeys
([
checkedKeys
[
0
]]);
setNowTitle
(
info
.
node
.
title
);
setLoading
(
false
);
}
}
};
useEffect
(()
=>
{
getChapterTreeList
();
},
[]);
useEffect
(()
=>
{
if
(
chapterId
)
{
setCheckedKeys
([
chapterId
]);
getChapterVal
();
}
},
[
chapterId
]);
// 保存方法
const
saveContent
=
async
()
=>
{
return
;
if
((
contentId
,
editorRef
.
current
,
editorRef
.
current
.
editor
,
!
loading
))
{
const
contentHtml
=
editorRef
.
current
.
editor
.
getHtml
();
const
newMd5
=
md5
(
contentHtml
);
clearInterval
(
saveInterRef
.
current
);
const
word_count
=
editorRef
.
current
.
editor
.
getText
().
length
;
// 保存
const
data
=
await
sectionEdit
({
id
:
contentId
,
content
:
contentHtml
,
word_count
,
});
if
(
data
)
{
if
((
data
.
code
&&
data
.
code
===
3000
)
||
data
.
code
===
"3000"
)
{
navigate
(
"/books/management/list"
);
return
;
}
else
{
setContentMd5
(
newMd5
);
dispatch
(
setAutosaveTime
(
Date
.
now
()));
}
}
}
};
useEffect
(()
=>
{
if
(
!
loading
&&
contentId
)
{
clearInterval
(
saveInterRef
.
current
);
}
else
{
clearInterval
(
saveInterRef
.
current
);
}
},
[
contentId
,
loading
,
contentMd5
,
html
]);
const
addChapterParent
=
()
=>
{
setEditKey
(
-
1
);
setEditValue
(
""
);
setParentId
(
-
1
);
};
const
delChapter
=
async
(
node
)
=>
{
const
childInKey
=
findParentLevelOne
(
gData
,
"key"
,
node
.
key
);
let
current
=
null
;
if
(
childInKey
&&
childInKey
.
children
&&
childInKey
.
children
.
length
>
0
)
{
const
index
=
findTreeToIndex
(
childInKey
.
children
,
"key"
,
node
.
key
);
if
(
childInKey
.
children
.
length
===
1
)
{
current
=
childInKey
;
}
else
{
// 前面还有
if
(
index
<=
childInKey
.
children
.
length
)
{
if
(
index
===
0
)
{
current
=
childInKey
.
children
[
index
+
1
];
}
else
{
current
=
childInKey
.
children
[
index
-
1
];
}
}
}
}
else
{
const
index
=
findTreeToIndex
(
gData
,
"key"
,
node
.
key
);
let
child
=
null
;
if
(
index
===
0
)
{
child
=
gData
[
index
+
1
];
}
else
{
child
=
gData
[
index
-
1
];
}
if
(
child
&&
child
.
children
&&
child
.
children
.
length
>
0
)
{
current
=
child
.
children
[
0
];
}
else
{
current
=
child
;
}
}
const
data
=
await
chapterDel
({
id
:
node
.
key
});
if
(
data
)
{
await
dispatch
(
setTreeChapter
({
saveBookId
:
id
,
saveChapterId
:
current
?
current
.
key
:
null
}),
);
await
getChapterTreeList
();
}
};
useEffect
(()
=>
{
if
(
gData
&&
gData
.
length
>
0
&&
treeChapter
&&
Object
.
entries
(
treeChapter
).
length
>
0
)
{
if
(
parseInt
(
treeChapter
.
saveBookId
)
===
parseInt
(
id
))
{
const
childInKey
=
findTreeElementByKey
(
gData
,
"key"
,
treeChapter
.
saveChapterId
);
if
(
childInKey
&&
Object
.
entries
(
childInKey
).
length
>
0
)
{
setChapterId
(
parseInt
(
treeChapter
.
saveChapterId
));
setCheckedKeys
([
parseInt
(
treeChapter
.
saveChapterId
)]);
setNowTitle
(
childInKey
.
title
);
}
}
}
},
[
gData
,
treeChapter
]);
let
chapterMenuItems
=
[
{
key
:
"1"
,
label
:
"展开全部"
},
// { key: '2', label: '添加子节' },
// { key: '5', label: '设置编者' },
// { key: '3', label: '编辑' },
// { key: '4', label: '删除' }
];
// if (!isAdminOrCreator) {
// chapterMenuItems = [{ key: '1', label: '展开全部' }]
// }
const
handleMenuClick
=
async
(
e
,
node
)
=>
{
e
.
domEvent
.
stopPropagation
();
setCurrentChapter
(
node
);
if
(
parseInt
(
e
.
key
)
===
1
)
{
// 展开全部
const
ids
=
findNodeById
([
node
],
"key"
,
node
.
key
);
const
expandedKeysTemp
=
[...
expandedKeys
,
node
.
key
,
...
ids
];
setExpandedKeys
(
expandedKeysTemp
);
}
else
if
(
parseInt
(
e
.
key
)
===
2
)
{
// 添加子节
setEditKey
(
-
1
);
setEditValue
(
""
);
setParentId
(
node
.
key
);
}
else
if
(
parseInt
(
e
.
key
)
===
3
)
{
// 编辑
setEditKey
(
node
.
key
);
setEditValue
(
node
.
title
);
setParentId
(
false
);
}
else
if
(
parseInt
(
e
.
key
)
===
4
)
{
await
modal
.
confirm
({
title
:
"删除确认"
,
content
:
`确认删除子节【
${
node
.
title
}
】?`
,
onOk
()
{
delChapter
(
node
);
},
});
}
else
if
(
e
.
key
==
5
)
{
setEditChapterEditorsIsOpen
(
true
);
}
};
// 章节名称
const
chapterTitleRender
=
(
node
)
=>
{
return
(
<
div
className=
"chapter-tree-item"
>
<
p
className=
"title"
>
{
node
.
title
}
</
p
>
<
Dropdown
trigger=
{
[
"click"
]
}
menu=
{
{
items
:
chapterMenuItems
,
onClick
:
(
e
)
=>
handleMenuClick
(
e
,
node
)
}
}
>
<
EllipsisOutlined
onClick=
{
(
e
)
=>
e
.
stopPropagation
()
}
/>
</
Dropdown
>
</
div
>
);
};
const
onDrop
=
async
(
info
)
=>
{
const
dropToGap
=
info
.
dropToGap
;
// true 为同级或最大级 false 为子级
const
nowDropNode
=
info
.
dragNode
.
key
;
const
sideDropNode
=
info
.
node
.
key
;
let
data
=
null
;
if
(
dropToGap
)
{
// 同级
const
dragOverGapBottom
=
info
.
node
.
dragOverGapBottom
;
const
dragOver
=
info
.
node
.
dragOver
;
const
dragOverGapTop
=
info
.
node
.
dragOverGapTop
;
if
(
!
dragOverGapTop
&&
!
dragOver
&&
!
dragOverGapBottom
)
{
// 拖到最底部
// 拖拽到最顶层
data
=
await
dragOrder
({
book_id
:
id
,
drag_node
:
nowDropNode
,
position_node
:
sideDropNode
,
drop_type
:
"after"
,
});
}
else
{
if
(
dragOverGapTop
)
{
// 拖拽到最顶层
data
=
await
dragOrder
({
book_id
:
id
,
drag_node
:
nowDropNode
,
position_node
:
sideDropNode
,
drop_type
:
"before"
,
});
}
else
if
(
dragOverGapBottom
)
{
data
=
await
dragOrder
({
book_id
:
id
,
drag_node
:
nowDropNode
,
position_node
:
sideDropNode
,
drop_type
:
"after"
,
});
}
}
}
else
{
// 子级
data
=
await
dragOrder
({
book_id
:
id
,
drag_node
:
nowDropNode
,
position_node
:
sideDropNode
,
drop_type
:
"inner"
,
});
}
if
(
data
)
{
await
getChapterTreeList
();
}
};
return
(
<
div
className=
"examine"
style=
{
{
padding
:
"20px"
,
height
:
"100vh"
,
boxSizing
:
"border-box"
}
}
>
<
div
className=
"content-box"
>
<
Row
gutter=
{
10
}
style=
{
{
height
:
"100%"
}
}
className=
"book-content-row"
>
<
Col
span=
{
isCollapse
?
1
:
4
}
className=
"book-content-tree"
>
<
div
className=
"border"
>
{
!
isCollapse
?
(
<>
<
Descriptions
layout=
"vertical"
rootClassName=
"section-left-top"
bordered
items=
{
[
{
key
:
"1"
,
label
:
(
<
Row
gutter=
{
5
}
justify=
{
"space-between"
}
style=
{
{
alignItems
:
"center"
}
}
>
<
Col
>
章节目录
</
Col
>
<
Col
>
{
isAdminOrCreator
&&
(
<
Button
type=
"text"
icon=
{
<
DiffOutlined
/>
}
onClick=
{
addChapterParent
}
></
Button
>
)
}
<
Button
type=
"text"
icon=
{
<
MenuFoldOutlined
/>
}
onClick=
{
()
=>
setisCollapse
(
true
)
}
></
Button
>
</
Col
>
</
Row
>
),
},
]
}
/>
{
gData
&&
gData
.
length
>
0
&&
(
<
Tree
className=
"draggable-tree"
onSelect=
{
handleSelect
}
defaultExpandAll
defaultExpandedKeys=
{
expandedKeys
}
expandedKeys=
{
expandedKeys
}
defaultSelectedKeys=
{
checkedKeys
}
selectedKeys=
{
checkedKeys
}
blockNode
disabled=
{
loading
}
treeData=
{
gData
}
onExpand=
{
onExpand
}
titleRender=
{
(
nodeData
)
=>
chapterTitleRender
(
nodeData
)
}
/>
)
}
</>
)
:
(
<
Descriptions
layout=
"vertical"
bordered
items=
{
[
{
key
:
"1"
,
label
:
(
<
Row
gutter=
{
5
}
justify=
{
"space-between"
}
style=
{
{
alignItems
:
"center"
}
}
>
<
Col
>
<
Button
type=
"text"
icon=
{
<
MenuUnfoldOutlined
/>
}
onClick=
{
()
=>
setisCollapse
(
false
)
}
></
Button
>
</
Col
>
</
Row
>
),
},
]
}
/>
)
}
</
div
>
</
Col
>
<
Col
span=
{
isCollapse
?
23
:
20
}
className=
"book-content-tree"
>
<
div
className=
"editor-right"
>
<
Spin
spinning=
{
loading
}
>
<
WangEditorCustomer
ref=
{
editorRef
}
chapterId=
{
chapterId
}
bookId=
{
id
}
contentId=
{
contentId
}
setHtml=
{
setHtml
}
html=
{
html
}
saveContent=
{
saveContent
}
gData=
{
gData
}
nowTitle=
{
nowTitle
}
disabled=
{
disabled
}
isView=
{
true
}
/>
</
Spin
>
</
div
>
</
Col
>
</
Row
>
</
div
>
</
div
>
);
};
export
default
Examine
;
export
{
default
}
from
"@/modules/student-book/views/StudentBookView"
;
apps/admin/src/pages/student/book/request.js
浏览文件 @
77cf6ebc
import
axios
from
"@/utils/request.js"
;
import
qs
from
"qs"
;
import
{
get
}
from
"lodash-es"
;
import
{
message
}
from
"antd"
;
// 用户信息
const
getUserInfo
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/user/user/getUserInfo"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 获取角色已选权限
const
getPowerByRoleId
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/user/role/getPowerByRoleId"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 章节列表
const
getAllList
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/chapter/getAllList"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
{
code
,
data
,
};
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 添加章节
const
chapterAdd
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/chapter/add"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
// message.success('添加成功');
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 章节详情
const
getInfoById
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/chapter/getInfoById"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 修改章节
const
chapterEdit
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/chapter/edit"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
message
.
success
(
msg
);
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 删除章节
const
chapterDel
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/chapter/del"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
message
.
success
(
msg
);
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 拖拽章节排序
const
dragOrder
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/chapter/dragOrder"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
message
.
success
(
msg
);
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 获取章节内容
const
getInfoByChapterId
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/section/getInfoByChapterId"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 添加章节内容
const
sectionAdd
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/section/add"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
message
.
success
(
msg
);
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 修改章节内容
const
sectionEdit
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/section/edit"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
// message.success(msg);
return
true
;
}
else
{
message
.
error
(
msg
);
return
{
code
,
msg
,
data
};
}
}
catch
(
error
)
{
return
false
;
}
};
// 获取章节的题库列表
const
getQuestionList
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/chapter/question/getQuestionList"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 移除课后题
const
questionDel
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/chapter/question/del"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 添加课后题
const
questionAdd
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/chapter/question/add"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 发布审核
const
release
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/release"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 模板列表
const
templateGetList
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/template/getList"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 模板导入
const
templateImport
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/template/import"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 书籍导入
const
teacherImport
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/import"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
// 保存历史记录
const
getRecordList
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/book/teacher/section/getRecordList"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
export
{
getAllList
,
chapterAdd
,
getInfoById
,
chapterEdit
,
chapterDel
,
dragOrder
,
getInfoByChapterId
,
sectionAdd
,
sectionEdit
,
getQuestionList
,
questionDel
,
questionAdd
,
release
,
templateGetList
,
templateImport
,
teacherImport
,
getRecordList
,
getPowerByRoleId
,
getUserInfo
,
};
export
*
from
"@/modules/student-book/request"
;
apps/admin/src/pages/teacher/index.jsx
浏览文件 @
77cf6ebc
import
React
from
"react"
;
import
{
Outlet
}
from
"react-router"
;
const
Teacher
=
()
=>
{
return
<
Outlet
></
Outlet
>;
};
export
default
Teacher
;
export
{
default
}
from
'@/modules/teachers/Layout'
apps/admin/src/pages/user-module/index.jsx
浏览文件 @
77cf6ebc
import
{
Outlet
}
from
"react-router"
;
import
"./index.less"
;
const
User
=
()
=>
{
return
(
<
div
className=
"user-content"
>
<
div
className=
"form-container"
>
<
div
className=
"left-container"
></
div
>
<
div
className=
"form-field-box"
>
<
div
className=
"form-field"
>
<
Outlet
/>
</
div
>
</
div
>
</
div
>
</
div
>
);
};
export
default
User
;
export
{
default
}
from
"@/modules/auth/views/AuthLayout"
;
apps/admin/src/pages/user-module/login/index.jsx
浏览文件 @
77cf6ebc
import
React
,
{
useState
,
useEffect
,
useRef
}
from
"react"
;
import
{
Form
,
Input
,
Button
,
Row
,
Col
,
Space
,
Image
,
message
}
from
"antd"
;
import
{
CloseOutlined
,
UserOutlined
,
LockOutlined
}
from
"@ant-design/icons"
;
import
{
useNavigate
}
from
"react-router"
;
import
{
checkSSOLogin
}
from
"../request/request"
;
import
"./index.less"
;
import
loginBtn
from
"@/assets/images/loginBtn.png"
;
import
loginBtn2
from
"@/assets/images/loginBtn2.png"
;
import
Icon1
from
"@/assets/login/1.png"
;
import
Icon2
from
"@/assets/login/2.png"
;
import
Icon3
from
"@/assets/login/3.png"
;
import
Icon4
from
"@/assets/login/4.png"
;
import
Icon5
from
"@/assets/login/5.png"
;
import
Icon6
from
"@/assets/login/6.png"
;
import
Icon7
from
"@/assets/login/7.png"
;
import
Icon8
from
"@/assets/login/8.png"
;
import
Icon9
from
"@/assets/login/9.png"
;
import
Icon10
from
"@/assets/login/10.png"
;
import
Icon11
from
"@/assets/login/11.png"
;
import
Icon12
from
"@/assets/login/12.png"
;
import
Icon13
from
"@/assets/login/13.png"
;
import
Icon14
from
"@/assets/login/14.png"
;
import
Icon15
from
"@/assets/login/15.png"
;
import
Icon16
from
"@/assets/login/16.png"
;
import
Icon17
from
"@/assets/login/17.png"
;
import
Icon18
from
"@/assets/login/18.png"
;
import
Icon19
from
"@/assets/login/19.png"
;
import
Icon20
from
"@/assets/login/20.png"
;
import
Icon21
from
"@/assets/login/21.png"
;
import
Icon22
from
"@/assets/login/22.png"
;
import
Icon23
from
"@/assets/login/23.png"
;
import
{
login
}
from
"./request"
;
import
logo
from
"@/assets/images/icon_logo_login.png"
;
import
{
RotateVerify
}
from
"../../../utils/captcha"
;
const
Login
=
()
=>
{
const
navigate
=
useNavigate
();
const
[
form
]
=
Form
.
useForm
();
const
[
isActive
,
setIsActive
]
=
useState
(
0
);
const
[
loading
,
setLoading
]
=
useState
(
false
);
const
[
captchaURL
,
setCaptchaURL
]
=
useState
(
""
);
const
[
captchaStatus
,
setCaptchaStatus
]
=
useState
(
false
);
const
[
captchaText
,
setCaptchaText
]
=
useState
(
"发送验证码"
);
const
[
showCaptcha
,
setShowCaptcha
]
=
useState
(
false
);
const
[
isLoggedIn
,
setIsLoggedIn
]
=
useState
(
false
);
//登录图片切换
const
[
isLoginBtn
,
setIsLoginBtn
]
=
useState
(
false
);
//是否点击了登录按钮
const
[
code
,
setCode
]
=
useState
(
""
);
const
[
disLf
,
setDisLf
]
=
useState
(
""
);
const
[
initialValues
,
setinitialValues
]
=
useState
({
tel
:
""
,
password
:
""
,
});
const
timer
=
useRef
();
const
getSSOInfo
=
async
()
=>
{
const
isSSOData
=
await
checkSSOLogin
();
if
(
isSSOData
.
run
===
"login"
)
{
}
else
if
(
isSSOData
.
token
)
{
localStorage
.
setItem
(
"kiwi.token"
,
isSSOData
.
token
);
navigate
(
"/"
);
return
;
}
};
useEffect
(()
=>
{
getSSOInfo
();
},
[]);
// 登录
const
toLayout
=
async
(
obj
)
=>
{
setLoading
(
true
);
if
(
isLoginBtn
)
{
const
data
=
await
login
({
...
obj
,
code
:
code
,
disLf
:
disLf
,
});
if
(
data
.
code
===
200
)
{
localStorage
.
setItem
(
"kiwi.token"
,
data
.
token
);
navigate
(
"/"
);
return
;
}
else
{
form
.
setFields
([{
name
:
"password"
,
errors
:
[
data
.
msg
]
}]);
}
}
else
{
message
.
error
(
"请先完成按钮验证"
);
}
// 刷新验证码
// await refreshCaptcha();
setLoading
(
false
);
};
const
handleStartVerification
=
()
=>
{
setShowCaptcha
(
true
);
};
// 点击×
const
handleGoBack
=
()
=>
{
setShowCaptcha
(
false
);
};
useEffect
(()
=>
{
if
(
showCaptcha
)
{
const
rotateWrapEle
=
document
.
querySelector
(
"#rotateWrap1"
);
// 确保 DOM 元素存在之后再进行初始化
if
(
rotateWrapEle
)
{
const
myRotateVerify
=
new
RotateVerify
(
"#rotateWrap1"
,
{
initText
:
"滑动将图片转正"
,
slideImage
:
[
Icon1
,
Icon2
,
Icon3
,
Icon4
,
Icon5
,
Icon6
,
Icon7
,
Icon8
,
Icon9
,
Icon10
,
Icon11
,
Icon12
,
Icon13
,
Icon14
,
Icon15
,
Icon16
,
Icon17
,
Icon18
,
Icon4
,
Icon19
,
Icon20
,
Icon21
,
Icon22
,
Icon23
,
],
initUrl
:
"/user/login/initData"
,
verifyUrl
:
"/user/login/checkVerify"
,
getSuccessState
:
function
(
data
)
{
console
.
log
(
"验证成功"
,
data
);
setDisLf
(
data
.
disLf
);
setCode
(
data
.
code
);
if
(
data
.
disLf
)
{
setShowCaptcha
(
false
);
setIsLoggedIn
(
true
);
setIsLoginBtn
(
true
);
}
},
});
// 如果需要,在这里处理卸载时清理资源
return
()
=>
{
console
.
log
(
myRotateVerify
.
initUrl
);
// 清理或销毁 RotateVerify 实例等操作
if
(
myRotateVerify
&&
typeof
myRotateVerify
.
destroy
===
"function"
)
{
myRotateVerify
.
destroy
();
}
};
}
}
},
[
showCaptcha
]);
return
(
<
div
className=
{
showCaptcha
?
"captcha-container"
:
"normal-container"
}
>
{
showCaptcha
&&
(
<
div
className=
"rotateWrap"
>
<
div
className=
"cuo"
onClick=
{
handleGoBack
}
>
<
CloseOutlined
/>
</
div
>
<
span
className=
"top-s"
>
身份验证
</
span
>
<
span
className=
"top-x"
>
拖动滑块,使图片角度为正
</
span
>
<
div
id=
"rotateWrap1"
>
<
div
className=
"rotateverify-contaniner"
>
<
div
className=
"rotate-can-wrap"
>
<
canvas
className=
"rotateCan rotate-can"
width=
"200"
height=
"200"
></
canvas
>
<
div
className=
"statusBg status-bg"
></
div
>
</
div
>
<
div
className=
"control-wrap slideDragWrap"
>
<
div
className=
"control-tips"
>
<
p
className=
"c-tips-txt cTipsTxt"
>
滑动将图片转正
</
p
>
</
div
>
<
div
className=
"control-bor-wrap controlBorWrap"
></
div
>
<
div
className=
"control-btn slideDragBtn"
>
{
/* <i className='control-btn-ico'></i> */
}
<
div
className=
"control-btn-ico"
></
div
>
</
div
>
</
div
>
</
div
>
</
div
>
</
div
>
)
}
{
!
showCaptcha
&&
(
<
div
className=
"img-logo"
>
<
img
src=
{
logo
}
className=
"img"
/>
</
div
>
)
}
{
!
showCaptcha
&&
<
div
className=
"login-header"
>
欢迎登录
</
div
>
}{
" "
}
{
!
showCaptcha
&&
(
<
div
className=
"form-data-container"
>
<
Form
form=
{
form
}
onFinish=
{
toLayout
}
autoComplete=
"off"
initialValues=
{
initialValues
}
>
<>
<
Form
.
Item
name=
"tel"
rules=
{
[
{
required
:
true
,
message
:
"请输入手机号!"
},
{
pattern
:
/^1
[
3-9
]\d
{9}$/
,
message
:
"请输入正确格式的手机号"
},
]
}
>
<
Input
autoComplete=
"off"
prefix=
{
<
UserOutlined
/>
}
size=
"large"
placeholder=
"请输入手机号"
/>
</
Form
.
Item
>
<
Form
.
Item
name=
"password"
rules=
{
[{
required
:
true
,
message
:
"请输入密码!"
}]
}
>
<
Input
.
Password
autoComplete=
"off"
prefix=
{
<
LockOutlined
/>
}
size=
"large"
placeholder=
"请输入密码"
/>
</
Form
.
Item
>
{
/* <Form.Item name='verify' rules={[{ required: true, message: '请输入验证码!' }]}>
<Row gutter={16}>
<Col span={16}>
<Input
prefix={<SetIcon src={IconCaptcha} />}
size='large'
placeholder='请输入验证码'
/>
</Col>
<Col span={8}>
<img
src={captchaURL}
alt='图形验证码'
className='captcha'
onClick={refreshCaptcha}
/>
</Col>
</Row>
</Form.Item> */
}
<
Form
.
Item
name=
"verify"
onClick=
{
handleStartVerification
}
>
<
div
style=
{
{
width
:
"100%"
,
height
:
"100%"
,
pointerEvents
:
"none"
}
}
>
{
isLoggedIn
?
(
<
Image
src=
{
loginBtn2
}
style=
{
{
width
:
"100%"
,
height
:
"100%"
,
}
}
/>
)
:
(
<
Image
src=
{
loginBtn
}
style=
{
{
width
:
"100%"
,
height
:
"100%"
,
}
}
/>
)
}
</
div
>
</
Form
.
Item
>
</>
<
Form
.
Item
className=
"login-submit-btn"
>
<
Button
htmlType=
"submit"
loading=
{
loading
}
type=
"primary"
size=
"large"
>
登录
</
Button
>
</
Form
.
Item
>
</
Form
>
</
div
>
)
}
</
div
>
);
};
export
default
Login
;
export
{
default
}
from
"@/modules/auth/views/LoginView"
;
apps/admin/src/pages/user-module/login/request/api.js
浏览文件 @
77cf6ebc
const
api
=
{
login
:
"/user/login"
,
getCode
:
"/user/login/verify"
,
};
export
default
api
;
export
{
default
}
from
"@/modules/auth/request/login-api"
;
apps/admin/src/pages/user-module/login/request/index.js
浏览文件 @
77cf6ebc
import
axios
from
"@/utils/request.js"
;
import
qs
from
"qs"
;
import
api
from
"./api.js"
;
import
{
get
}
from
"lodash-es"
;
import
{
message
}
from
"antd"
;
const
login
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
api
.
login
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
msg
===
"Undefined index: verify"
)
{
message
.
error
(
"请先点击按钮进行验证"
);
return
false
;
}
if
(
code
===
200
)
{
return
{
code
,
...
data
,
};
}
else
{
message
.
error
(
msg
);
return
{
msg
,
};
}
}
catch
(
error
)
{
console
.
error
(
error
);
let
msg
=
get
(
error
.
response
,
"data.msg"
,
""
);
if
(
msg
)
{
message
.
error
(
msg
);
}
return
false
;
}
};
export
{
login
};
export
*
from
"@/modules/auth/request/login"
;
apps/admin/src/pages/user-module/request/api.js
浏览文件 @
77cf6ebc
const
api
=
{
getCaptcha
:
"/users/generate_image_code"
,
// 获取验证码 GET
sendSMS
:
"/docapi/users/send_sms"
,
// 发送手机验证码
loginByPhone
:
"/docapi/users/phone_login"
,
// 手机号码形式登录
loginByUsername
:
"/docapi/users/login"
,
// 用户名密码登录
checkSSOLogin
:
"/user/checkSsoLogin"
,
// sso 登录
};
export
default
api
;
export
{
default
}
from
"@/modules/auth/request/sso-api"
;
apps/admin/src/pages/user-module/request/request.jsx
浏览文件 @
77cf6ebc
import
axios
from
"@/utils/request"
;
import
{
message
}
from
"antd"
;
import
api
from
"./api"
;
import
{
get
}
from
"lodash-es"
;
import
qs
from
"qs"
;
// 获取图形验证码
const
getCaptcha
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
get
(
api
.
getCaptcha
,
obj
);
const
status
=
get
(
res
,
"status"
,
0
);
const
data
=
get
(
res
,
"data"
,
""
);
if
(
status
===
200
)
{
return
data
;
}
}
catch
(
error
)
{
console
.
error
(
error
);
let
msg
=
get
(
error
.
response
,
"data.msg"
,
""
);
if
(
msg
)
{
message
.
error
(
msg
);
}
return
false
;
}
};
// 发送短信验证码
const
sendSMS
=
async
(
data
)
=>
{
try
{
const
res
=
await
axios
.
post
(
api
.
sendSMS
,
data
);
const
code
=
get
(
res
,
"data.code"
,
0
);
const
msg
=
get
(
res
,
"data.message"
,
"发送失败"
);
if
(
code
===
200
)
{
message
.
success
(
"验证码发送成功!"
);
return
true
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
console
.
error
(
error
);
let
msg
=
get
(
error
.
response
,
"data.msg"
,
""
);
if
(
msg
)
{
message
.
error
(
msg
);
}
return
false
;
}
};
// 用户名登录
const
userNameLogin
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
api
.
loginByUsername
,
obj
);
const
code
=
get
(
res
,
"data.code"
,
0
);
const
msg
=
get
(
res
,
"data.message"
,
"登录失败"
);
const
data
=
get
(
res
,
"data.data"
,
""
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
console
.
error
(
error
);
let
msg
=
get
(
error
.
response
,
"data.msg"
,
""
);
if
(
msg
)
{
message
.
error
(
msg
);
}
return
false
;
}
};
// 手机号登录
const
phoneLogin
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
api
.
loginByPhone
,
obj
);
const
code
=
get
(
res
,
"data.code"
,
0
);
const
msg
=
get
(
res
,
"data.message"
,
"登录失败"
);
const
data
=
get
(
res
,
"data.data"
,
""
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
console
.
error
(
error
);
let
msg
=
get
(
error
.
response
,
"data.msg"
,
""
);
if
(
msg
)
{
message
.
error
(
msg
);
}
return
false
;
}
};
const
checkSSOLogin
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
api
.
checkSSOLogin
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"数据获取失败!"
);
if
(
msg
===
"Undefined index: verify"
)
{
message
.
error
(
"请先点击按钮进行验证"
);
return
false
;
}
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
console
.
error
(
error
);
let
msg
=
get
(
error
.
response
,
"data.msg"
,
""
);
if
(
msg
)
{
message
.
error
(
msg
);
}
return
false
;
}
};
export
{
getCaptcha
,
sendSMS
,
userNameLogin
,
phoneLogin
,
checkSSOLogin
};
export
*
from
"@/modules/auth/request/sso"
;
apps/admin/src/pages/user-module/userInfo/index.jsx
浏览文件 @
77cf6ebc
import
{
useState
,
useEffect
}
from
"react"
;
import
{
Form
,
Input
,
Button
,
Space
,
Row
,
Col
,
Drawer
}
from
"antd"
;
import
users
from
"@/assets/images/icon/userinfo.png"
;
import
{
changePass
,
changeInfo
,
getUserInfo
}
from
"@/layout/request"
;
import
{
useNavigate
}
from
"react-router"
;
import
UploadImage
from
"@/components/uploadImage"
;
const
UserInfo
=
()
=>
{
const
navigator
=
useNavigate
();
const
[
imageUrl
,
setImageUrl
]
=
useState
(
""
);
const
[
form
]
=
Form
.
useForm
();
const
[
form2
]
=
Form
.
useForm
();
const
[
showModal
,
setShowModal
]
=
useState
(
false
);
const
fetchData
=
async
(
obj
)
=>
{
try
{
const
userData
=
await
getUserInfo
(
obj
);
form
.
setFieldsValue
({
real_name
:
userData
.
real_name
,
tel
:
userData
.
tel
,
pic
:
userData
.
pic
,
});
const
imageUrl
=
userData
.
pic
;
setImageUrl
(
imageUrl
);
}
catch
(
error
)
{
console
.
error
(
"Error fetching user data:"
,
error
);
}
};
useEffect
(()
=>
{
fetchData
();
},
[]);
const
changePasswrd
=
async
(
obj
)
=>
{
await
changePass
(
obj
);
setShowModal
(
false
);
};
const
submitForm
=
async
(
obj
)
=>
{
const
updatedData
=
await
changeInfo
({
...
obj
});
if
(
updatedData
)
{
await
fetchData
();
}
};
return
(
<
div
style=
{
{
padding
:
"20px 0"
}
}
>
<
Form
form=
{
form
}
labelCol=
{
{
span
:
2
}
}
onFinish=
{
submitForm
}
>
<
Form
.
Item
label=
"头像"
className=
"uploadPar"
name=
"pic"
>
<
UploadImage
value=
{
imageUrl
?
imageUrl
:
users
}
onChange=
{
(
url
)
=>
setImageUrl
(
url
)
}
></
UploadImage
>
</
Form
.
Item
>
<
Form
.
Item
label=
"管理员名称"
name=
"real_name"
rules=
{
[{
required
:
true
,
message
:
"请输入管理员名称"
}]
}
>
<
Input
autoComplete=
"off"
placeholder=
"请输入管理员名称"
style=
{
{
width
:
200
}
}
></
Input
>
</
Form
.
Item
>
<
Form
.
Item
label=
"手机号"
name=
"tel"
rules=
{
[{
required
:
true
,
message
:
"请输入手机号"
}]
}
>
<
Input
autoComplete=
"off"
placeholder=
"请输入手机号"
style=
{
{
width
:
200
}
}
></
Input
>
</
Form
.
Item
>
<
Form
.
Item
label=
"密码"
>
<
Button
type=
"primary"
ghost
onClick=
{
()
=>
setShowModal
(
true
)
}
>
修改密码
</
Button
>
</
Form
.
Item
>
<
Form
.
Item
wrapperCol=
{
{
offset
:
2
}
}
>
<
Space
size=
{
20
}
>
<
Button
onClick=
{
()
=>
navigator
(
-
1
)
}
>
取消
</
Button
>
<
Button
type=
"primary"
htmlType=
"submit"
>
保存
</
Button
>
</
Space
>
</
Form
.
Item
>
</
Form
>
<
Drawer
open=
{
showModal
}
onClose=
{
()
=>
{
setShowModal
(
false
);
}
}
mask=
{
false
}
maskClosable=
{
false
}
footer=
{
null
}
centered
destroyOnClose
keyboard=
{
false
}
>
<
Form
labelCol=
{
{
span
:
5
}
}
form=
{
form2
}
onFinish=
{
changePasswrd
}
>
<
Form
.
Item
label=
"旧密码"
name=
"old_password"
rules=
{
[{
required
:
true
,
message
:
"请输入旧密码"
}]
}
>
<
Input
.
Password
autoComplete=
"off"
placeholder=
"请输入旧密码"
></
Input
.
Password
>
</
Form
.
Item
>
<
Form
.
Item
label=
"新密码"
name=
"password"
rules=
{
[
{
required
:
true
,
message
:
"请输入新密码"
},
{
pattern
:
/^
((?=
.*
\d)(?=
.*
[
A-Z
])(?=
.*
[
a-z
]))
|
((?=
.*
\d)(?=
.*
[
A-Z
])(?=
.*
[\W
_
]))
|
((?=
.*
\d)(?=
.*
[
a-z
])(?=
.*
[\W
_
]))
|
((?=
.*
[
A-Z
])(?=
.*
[
a-z
])(?=
.*
[\W
_
]))[
a-zA-Z
\d\W
_
]
{8,12}$/
,
message
:
"密码至少满足三种格式(大写字母、小写字母、数字、特殊符号), 长度为8~12位字符!"
,
trigger
:
[
"change"
,
"blur"
],
},
{
validator
(
_
,
value
)
{
if
(
!
value
||
form2
.
getFieldValue
(
"re_password"
)
===
value
)
{
form2
.
setFields
([{
name
:
"re_password"
,
errors
:
[
""
]
}]);
return
Promise
.
resolve
();
}
form2
.
setFields
([{
name
:
"re_password"
,
errors
:
[
"两次输入的密码不一致"
]
}]);
return
Promise
.
reject
();
},
},
]
}
>
<
Input
.
Password
autoComplete=
"off"
placeholder=
"请输入新密码"
></
Input
.
Password
>
</
Form
.
Item
>
<
Form
.
Item
label=
"确认新密码"
name=
"re_password"
rules=
{
[
{
required
:
true
,
message
:
"请确认新密码"
},
{
pattern
:
/^
((?=
.*
\d)(?=
.*
[
A-Z
])(?=
.*
[
a-z
]))
|
((?=
.*
\d)(?=
.*
[
A-Z
])(?=
.*
[\W
_
]))
|
((?=
.*
\d)(?=
.*
[
a-z
])(?=
.*
[\W
_
]))
|
((?=
.*
[
A-Z
])(?=
.*
[
a-z
])(?=
.*
[\W
_
]))[
a-zA-Z
\d\W
_
]
{8,12}$/
,
message
:
"密码至少满足三种格式(大写字母、小写字母、数字、特殊符号), 长度为8~12位字符!"
,
trigger
:
[
"change"
,
"blur"
],
},
{
validator
(
_
,
value
)
{
if
(
!
value
||
form2
.
getFieldValue
(
"password"
)
===
value
)
{
return
Promise
.
resolve
();
}
return
Promise
.
reject
(
new
Error
(
"两次输入的密码不一致"
));
},
},
]
}
>
<
Input
.
Password
autoComplete=
"off"
placeholder=
"请确认新密码"
></
Input
.
Password
>
</
Form
.
Item
>
<
Form
.
Item
wrapperCol=
{
{
offset
:
5
}
}
>
<
Row
justify=
{
"center"
}
>
<
Space
size=
{
20
}
>
<
Col
>
<
Button
onClick=
{
()
=>
setShowModal
(
false
)
}
>
取消
</
Button
>
</
Col
>
<
Col
>
<
Button
type=
"primary"
htmlType=
"submit"
>
确认
</
Button
>
</
Col
>
</
Space
>
</
Row
>
</
Form
.
Item
>
</
Form
>
</
Drawer
>
</
div
>
);
};
export
default
UserInfo
;
export
{
default
}
from
"@/modules/account/views/AccountView"
;
apps/admin/src/pages/user-module/userInfo/requet.js
浏览文件 @
77cf6ebc
import
axios
from
"@/utils/request.js"
;
import
qs
from
"qs"
;
import
{
message
}
from
"antd"
;
import
{
get
}
from
"lodash-es"
;
// oss
const
getAliOSSSTSToken
=
async
(
obj
)
=>
{
try
{
const
res
=
await
axios
.
post
(
"/common/OssUpload/getUploadToken"
,
qs
.
stringify
(
obj
));
const
code
=
get
(
res
,
"data.code"
,
0
);
const
data
=
get
(
res
,
"data.data"
,
[]);
const
msg
=
get
(
res
,
"data.message"
,
"获取OSS令牌失败!"
);
if
(
code
===
200
)
{
return
data
;
}
else
{
message
.
error
(
msg
);
return
false
;
}
}
catch
(
error
)
{
return
false
;
}
};
export
{
getAliOSSSTSToken
};
export
*
from
"@/modules/account/oss"
;
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论