同步
This commit is contained in:
4
Cunkebao/.gitignore
vendored
4
Cunkebao/.gitignore
vendored
@@ -5,3 +5,7 @@ yarn.lock
|
|||||||
.env
|
.env
|
||||||
.DS_Store
|
.DS_Store
|
||||||
dist/*
|
dist/*
|
||||||
|
.cursorindexingignore
|
||||||
|
*.zip
|
||||||
|
.idea/
|
||||||
|
.next/
|
||||||
|
|||||||
7
Server/.gitignore
vendored
7
Server/.gitignore
vendored
@@ -9,3 +9,10 @@ vendor
|
|||||||
/404.html
|
/404.html
|
||||||
# SpecStory explanation file
|
# SpecStory explanation file
|
||||||
.specstory/.what-is-this.md
|
.specstory/.what-is-this.md
|
||||||
|
/
|
||||||
|
.cursorindexingignore
|
||||||
|
.user.ini
|
||||||
|
nginx.htaccess
|
||||||
|
.cursor/
|
||||||
|
thinkphp/
|
||||||
|
public/static/
|
||||||
|
|||||||
@@ -1,40 +0,0 @@
|
|||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<title>恭喜,站点创建成功!</title>
|
|
||||||
<style>
|
|
||||||
|
|
||||||
.container {
|
|
||||||
width: 60%;
|
|
||||||
margin: 10% auto 0;
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
padding: 2% 5%;
|
|
||||||
border-radius: 10px
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul li {
|
|
||||||
line-height: 2.3
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #20a53a
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<h1>恭喜, 站点创建成功!</h1>
|
|
||||||
<h3>这是默认index.html,本页面由系统自动生成</h3>
|
|
||||||
<ul>
|
|
||||||
<li>本页面在FTP根目录下的index.html</li>
|
|
||||||
<li>您可以修改、删除或覆盖本页面</li>
|
|
||||||
<li>FTP相关信息,请到“面板系统后台 > FTP” 查看</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
Pa_Vv8VOhdcPRmNHh_J_flKMHVwUywOIwT-F9iiA8p4.2wKeISBpPoGcCt72TdgVrBMxtnnieXof2OZkRdHKCJI
|
|
||||||
4
Store_vue/.gitignore
vendored
Normal file
4
Store_vue/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
.cursorindexingignore
|
||||||
|
.specstory/
|
||||||
|
node_modules/
|
||||||
|
unpackage/
|
||||||
4
Touchkebao/.gitignore
vendored
4
Touchkebao/.gitignore
vendored
@@ -3,4 +3,6 @@ dist/
|
|||||||
build/
|
build/
|
||||||
yarn.lock
|
yarn.lock
|
||||||
.env
|
.env
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
.specstory/
|
||||||
|
.cursorindexingignore
|
||||||
|
|||||||
@@ -31,41 +31,86 @@
|
|||||||
|
|
||||||
.accountCard {
|
.accountCard {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 120px;
|
max-width: 140px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border: 2px solid #f0f0f0;
|
border: 2px solid #e8e8e8;
|
||||||
border-radius: 8px;
|
border-radius: 12px;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
background: #fff;
|
||||||
|
|
||||||
&:hover:not(.disabled) {
|
&:hover:not(.disabled):not(.selected) {
|
||||||
border-color: #d9d9d9;
|
border-color: #d9d9d9;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
||||||
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.selected {
|
&.selected {
|
||||||
border-color: #1890ff;
|
border-color: #1890ff;
|
||||||
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
background: linear-gradient(135deg, #e6f7ff 0%, #f0f8ff 100%);
|
||||||
|
box-shadow:
|
||||||
|
0 0 0 3px rgba(24, 144, 255, 0.15),
|
||||||
|
0 4px 12px rgba(24, 144, 255, 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
&.disabled {
|
&.disabled {
|
||||||
opacity: 0.6;
|
opacity: 0.5;
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
background-color: #fafafa;
|
background-color: #fafafa;
|
||||||
|
border-color: #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
:global(.ant-card-body) {
|
padding: 20px 16px;
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.accountInfo {
|
.accountInfo {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.avatarWrapper {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
.accountAvatar {
|
||||||
|
border: 3px solid #e8e8e8;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.selectedIndicator {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -2px;
|
||||||
|
right: -2px;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
background: #1890ff;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: 3px solid #fff;
|
||||||
|
box-shadow: 0 2px 8px rgba(24, 144, 255, 0.4);
|
||||||
|
|
||||||
|
.checkIcon {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: bold;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.accountCard.selected .avatarWrapper .accountAvatar {
|
||||||
|
border-color: #1890ff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
.accountName {
|
.accountName {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: #262626;
|
color: #262626;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
|
transition: color 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.usageBadge {
|
.usageBadge {
|
||||||
@@ -75,6 +120,7 @@
|
|||||||
height: 20px;
|
height: 20px;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
|
padding: 0 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,6 +130,19 @@
|
|||||||
margin-top: 4px;
|
margin-top: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 选中状态下的样式增强
|
||||||
|
&.selected .accountInfo {
|
||||||
|
.accountName {
|
||||||
|
color: #1890ff;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatarWrapper .accountAvatar {
|
||||||
|
border-color: #1890ff;
|
||||||
|
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.2);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect, useCallback } from "react";
|
import React, { useState, useEffect, useCallback } from "react";
|
||||||
import { Button, Input, Select, message, Card, Badge } from "antd";
|
import { Button, Input, Select, message, Badge, Avatar } from "antd";
|
||||||
import { useCkChatStore } from "@/store/module/ckchat/ckchat";
|
import { useCustomerStore, updateCustomerList } from "@weChatStore/customer";
|
||||||
|
import { getCustomerList } from "@apiModule/wechat";
|
||||||
import { addMoment } from "./api";
|
import { addMoment } from "./api";
|
||||||
import styles from "./MomentPublish.module.scss";
|
import styles from "./MomentPublish.module.scss";
|
||||||
import { KfUserListData } from "@/pages/pc/ckbox/data";
|
import { KfUserListData } from "@/pages/pc/ckbox/data";
|
||||||
@@ -24,44 +25,60 @@ const MomentPublish: React.FC<MomentPublishProps> = ({ onPublishSuccess }) => {
|
|||||||
const [linkUrl, setLinkUrl] = useState<string>("");
|
const [linkUrl, setLinkUrl] = useState<string>("");
|
||||||
const [accounts, setAccounts] = useState<KfUserListData[]>([]);
|
const [accounts, setAccounts] = useState<KfUserListData[]>([]);
|
||||||
const [loading, setLoading] = useState<boolean>(false);
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [customerListLoading, setCustomerListLoading] =
|
||||||
|
useState<boolean>(false);
|
||||||
|
|
||||||
// 从store获取客服列表
|
// 从store获取客服列表
|
||||||
const kfUserList = useCkChatStore(state => state.kfUserList);
|
const customerList = useCustomerStore(state => state.customerList);
|
||||||
|
|
||||||
|
// 初始化获取客服列表
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchCustomerList = async () => {
|
||||||
|
setCustomerListLoading(true);
|
||||||
|
try {
|
||||||
|
const res = await getCustomerList();
|
||||||
|
updateCustomerList(res);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取客服列表失败:", error);
|
||||||
|
message.error("获取客服列表失败");
|
||||||
|
} finally {
|
||||||
|
setCustomerListLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果客服列表为空,则获取
|
||||||
|
if (customerList.length === 0) {
|
||||||
|
fetchCustomerList();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
// 获取账号使用情况
|
// 获取账号使用情况
|
||||||
const fetchAccountUsage = useCallback(async () => {
|
const fetchAccountUsage = useCallback(async () => {
|
||||||
if (kfUserList.length === 0) return;
|
if (customerList.length === 0) return;
|
||||||
|
|
||||||
// 直接使用客服列表数据,不需要额外的API调用
|
// 直接使用客服列表数据,不需要额外的API调用
|
||||||
const accountData = kfUserList.map((kf, index) => ({
|
const accountData = customerList.map((customer, index) => ({
|
||||||
...kf,
|
...customer,
|
||||||
name: kf.nickname || `客服${index + 1}`,
|
name: customer.nickname || `客服${index + 1}`,
|
||||||
isSelected: selectedAccounts.includes(kf.id.toString()),
|
isSelected: selectedAccounts.includes(customer.id.toString()),
|
||||||
isDisabled: kf.momentsNum >= kf.momentsMax,
|
isDisabled: customer.momentsNum >= customer.momentsMax,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
setAccounts(accountData);
|
setAccounts(accountData);
|
||||||
}, [kfUserList, selectedAccounts]);
|
}, [customerList, selectedAccounts]);
|
||||||
|
|
||||||
// 如果没有选中的账号且有可用账号,自动选择第一个可用账号
|
// 移除自动选择逻辑,允许用户手动选择或取消选择
|
||||||
useEffect(() => {
|
|
||||||
if (selectedAccounts.length === 0 && accounts.length > 0) {
|
|
||||||
const firstAvailable = accounts.find(acc => !acc.isDisabled);
|
|
||||||
if (firstAvailable) {
|
|
||||||
setSelectedAccounts([firstAvailable.id.toString()]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [accounts, selectedAccounts.length]);
|
|
||||||
|
|
||||||
// 当客服列表变化时,重新获取使用情况
|
// 当客服列表变化时,重新获取使用情况
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchAccountUsage();
|
fetchAccountUsage();
|
||||||
}, [kfUserList, selectedAccounts, fetchAccountUsage]);
|
}, [customerList, selectedAccounts, fetchAccountUsage]);
|
||||||
|
|
||||||
const handleAccountSelect = (accountId: string) => {
|
const handleAccountSelect = (accountId: string) => {
|
||||||
const account = accounts.find(acc => acc.id.toString() === accountId);
|
const account = accounts.find(acc => acc.id.toString() === accountId);
|
||||||
if (!account || account.isDisabled) return;
|
if (!account || account.isDisabled) return;
|
||||||
|
|
||||||
|
// 允许取消选择:如果已选中则取消,未选中则选中
|
||||||
setSelectedAccounts(prev =>
|
setSelectedAccounts(prev =>
|
||||||
prev.includes(accountId)
|
prev.includes(accountId)
|
||||||
? prev.filter(id => id !== accountId)
|
? prev.filter(id => id !== accountId)
|
||||||
@@ -146,8 +163,13 @@ const MomentPublish: React.FC<MomentPublishProps> = ({ onPublishSuccess }) => {
|
|||||||
setLinkImage("");
|
setLinkImage("");
|
||||||
setLinkUrl("");
|
setLinkUrl("");
|
||||||
setTimingTime("");
|
setTimingTime("");
|
||||||
// 重新获取账号使用情况
|
// 重新获取客服列表以更新使用情况
|
||||||
await fetchAccountUsage();
|
try {
|
||||||
|
const res = await getCustomerList();
|
||||||
|
updateCustomerList(res);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("刷新客服列表失败:", error);
|
||||||
|
}
|
||||||
// 触发父组件的刷新回调
|
// 触发父组件的刷新回调
|
||||||
onPublishSuccess?.();
|
onPublishSuccess?.();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -167,7 +189,7 @@ const MomentPublish: React.FC<MomentPublishProps> = ({ onPublishSuccess }) => {
|
|||||||
<h4 className={styles.sectionTitle}>选择发布账号</h4>
|
<h4 className={styles.sectionTitle}>选择发布账号</h4>
|
||||||
<div className={styles.accountList}>
|
<div className={styles.accountList}>
|
||||||
{accounts.map(account => (
|
{accounts.map(account => (
|
||||||
<Card
|
<div
|
||||||
key={account.id}
|
key={account.id}
|
||||||
className={`${styles.accountCard} ${
|
className={`${styles.accountCard} ${
|
||||||
selectedAccounts.includes(account.id.toString())
|
selectedAccounts.includes(account.id.toString())
|
||||||
@@ -177,6 +199,23 @@ const MomentPublish: React.FC<MomentPublishProps> = ({ onPublishSuccess }) => {
|
|||||||
onClick={() => handleAccountSelect(account.id.toString())}
|
onClick={() => handleAccountSelect(account.id.toString())}
|
||||||
>
|
>
|
||||||
<div className={styles.accountInfo}>
|
<div className={styles.accountInfo}>
|
||||||
|
<div className={styles.avatarWrapper}>
|
||||||
|
<Avatar
|
||||||
|
src={account.avatar}
|
||||||
|
size={56}
|
||||||
|
className={styles.accountAvatar}
|
||||||
|
style={{
|
||||||
|
backgroundColor: !account.avatar ? "#1890ff" : undefined,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{!account.avatar && account.name?.charAt(0)}
|
||||||
|
</Avatar>
|
||||||
|
{selectedAccounts.includes(account.id.toString()) && (
|
||||||
|
<div className={styles.selectedIndicator}>
|
||||||
|
<span className={styles.checkIcon}>✓</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
<div className={styles.accountName}>{account.name}</div>
|
<div className={styles.accountName}>{account.name}</div>
|
||||||
<Badge
|
<Badge
|
||||||
count={`${account.momentsNum}/${account.momentsMax}`}
|
count={`${account.momentsNum}/${account.momentsMax}`}
|
||||||
@@ -189,7 +228,7 @@ const MomentPublish: React.FC<MomentPublishProps> = ({ onPublishSuccess }) => {
|
|||||||
<div className={styles.limitText}>今日已达上限</div>
|
<div className={styles.limitText}>今日已达上限</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
open: true,
|
open: true,
|
||||||
port: 3000,
|
port: 8888,
|
||||||
host: "0.0.0.0",
|
host: "0.0.0.0",
|
||||||
},
|
},
|
||||||
build: {
|
build: {
|
||||||
|
|||||||
Reference in New Issue
Block a user