diff --git a/Cunkebao/app/content/new/page.tsx b/Cunkebao/app/content/new/page.tsx index 4ecf13c4..d39a3d68 100644 --- a/Cunkebao/app/content/new/page.tsx +++ b/Cunkebao/app/content/new/page.tsx @@ -14,6 +14,10 @@ import { DateRangePicker } from "@/components/ui/date-range-picker" import { WechatFriendSelector } from "@/components/WechatFriendSelector" import { WechatGroupSelector } from "@/components/WechatGroupSelector" import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from "@/components/ui/accordion" +import { api } from "@/lib/api" +import { showToast } from "@/lib/toast" +import { format } from "date-fns" +import { zhCN } from "date-fns/locale" interface WechatFriend { id: string @@ -33,6 +37,12 @@ interface WechatGroup { customer: string } +interface ApiResponse { + code: number + msg: string + data: T +} + export default function NewContentLibraryPage() { const router = useRouter() const [formData, setFormData] = useState({ @@ -51,6 +61,7 @@ export default function NewContentLibraryPage() { const [isWechatFriendSelectorOpen, setIsWechatFriendSelectorOpen] = useState(false) const [isWechatGroupSelectorOpen, setIsWechatGroupSelectorOpen] = useState(false) + const [loading, setLoading] = useState(false) const removeFriend = (friendId: string) => { setFormData((prev) => ({ @@ -66,6 +77,54 @@ export default function NewContentLibraryPage() { })) } + const handleSubmit = async () => { + if (!formData.name) { + showToast("请输入内容库名称", "error") + return + } + + if (formData.sourceType === "friends" && formData.selectedFriends.length === 0) { + showToast("请选择微信好友", "error") + return + } + + if (formData.sourceType === "groups" && formData.selectedGroups.length === 0) { + showToast("请选择聊天群", "error") + return + } + + setLoading(true) + try { + const payload = { + name: formData.name, + sourceType: formData.sourceType === "friends" ? 1 : 2, + friends: formData.selectedFriends.map(f => f.id), + groups: formData.selectedGroups.map(g => g.id), + keywordInclude: formData.keywordsInclude.split(",").map(k => k.trim()).filter(Boolean), + keywordExclude: formData.keywordsExclude.split(",").map(k => k.trim()).filter(Boolean), + aiPrompt: formData.useAI ? formData.aiPrompt : "", + timeEnabled: formData.startDate && formData.endDate ? 1 : 0, + startTime: formData.startDate ? format(new Date(formData.startDate), "yyyy-MM-dd") : "", + endTime: formData.endDate ? format(new Date(formData.endDate), "yyyy-MM-dd") : "", + status: formData.enabled ? 1 : 0 + } + + const response = await api.post("/v1/content/library/create", payload) + + if (response.code === 200) { + showToast("创建成功", "success") + router.push("/content") + } else { + showToast(response.msg || "创建失败", "error") + } + } catch (error: any) { + console.error("创建内容库失败:", error) + showToast(error?.message || "请检查网络连接", "error") + } finally { + setLoading(false) + } + } + return (
@@ -98,7 +157,7 @@ export default function NewContentLibraryPage() { setFormData({ ...formData, sourceType: value })} + onValueChange={(value) => setFormData({ ...formData, sourceType: value as "friends" | "groups" })} className="mt-1.5" > @@ -224,14 +283,16 @@ export default function NewContentLibraryPage() { { - if (range?.from) { - setFormData({ - ...formData, - startDate: range.from.toISOString(), - endDate: range.to?.toISOString() || "", - }) - } + setFormData({ + ...formData, + startDate: range?.from ? format(range.from, "yyyy-MM-dd") : "", + endDate: range?.to ? format(range.to, "yyyy-MM-dd") : "", + }) }} + value={formData.startDate && formData.endDate ? { + from: new Date(formData.startDate), + to: new Date(formData.endDate) + } : undefined} />
@@ -246,11 +307,22 @@ export default function NewContentLibraryPage() {
- -
diff --git a/Cunkebao/app/content/page.tsx b/Cunkebao/app/content/page.tsx index 84fb3d75..d6f99092 100644 --- a/Cunkebao/app/content/page.tsx +++ b/Cunkebao/app/content/page.tsx @@ -1,6 +1,6 @@ "use client" -import { useState } from "react" +import { useState, useEffect, useCallback } from "react" import { ChevronLeft, Filter, Search, RefreshCw, Plus, Edit, Trash2, Eye, MoreVertical } from "lucide-react" import { Card } from "@/components/ui/card" import { Button } from "@/components/ui/button" @@ -10,6 +10,19 @@ import { Badge } from "@/components/ui/badge" import { useRouter } from "next/navigation" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" import Image from "next/image" +import { api } from "@/lib/api" +import { showToast } from "@/lib/toast" + +interface ApiResponse { + code: number + msg: string + data: T +} + +interface LibraryListResponse { + list: ContentLibrary[] + total: number +} interface ContentLibrary { id: string @@ -24,75 +37,129 @@ interface ContentLibrary { itemCount: number lastUpdated: string enabled: boolean + // 新增字段 + sourceFriends: string[] + sourceGroups: string[] + keywordInclude: string[] + keywordExclude: string[] + isEnabled: number + aiPrompt: string + timeEnabled: number + timeStart: string + timeEnd: string + status: number + createTime: string + updateTime: string + sourceType: number } export default function ContentLibraryPage() { const router = useRouter() - const [libraries, setLibraries] = useState([ - { - id: "129", - name: "微信好友广告", - source: "friends", - targetAudience: [ - { id: "1", nickname: "张三", avatar: "/placeholder.svg?height=40&width=40" }, - { id: "2", nickname: "李四", avatar: "/placeholder.svg?height=40&width=40" }, - { id: "3", nickname: "王五", avatar: "/placeholder.svg?height=40&width=40" }, - ], - creator: "海尼", - itemCount: 0, - lastUpdated: "2024-02-09 12:30", - enabled: false, - }, - { - id: "127", - name: "开发群", - source: "groups", - targetAudience: [{ id: "4", nickname: "开发群1", avatar: "/placeholder.svg?height=40&width=40" }], - creator: "karuo", - itemCount: 0, - lastUpdated: "2024-02-09 12:30", - enabled: true, - }, - ]) - + const [libraries, setLibraries] = useState([]) const [searchQuery, setSearchQuery] = useState("") const [activeTab, setActiveTab] = useState("all") + const [loading, setLoading] = useState(false) + + // 获取内容库列表 + const fetchLibraries = useCallback(async () => { + setLoading(true) + try { + const queryParams = new URLSearchParams({ + page: '1', + limit: '100', + ...(searchQuery ? { keyword: searchQuery } : {}), + ...(activeTab !== 'all' ? { sourceType: activeTab === 'friends' ? '1' : '2' } : {}) + }) + const response = await api.get>(`/v1/content/library/list?${queryParams.toString()}`) + + if (response.code === 200 && response.data) { + // 转换数据格式以匹配原有UI + const transformedLibraries = response.data.list.map((item) => { + const transformedItem: ContentLibrary = { + id: item.id, + name: item.name, + source: item.sourceType === 1 ? "friends" : "groups", + targetAudience: [ + ...(item.sourceFriends || []).map((id: string) => ({ id, nickname: `好友${id}`, avatar: "/placeholder.svg" })), + ...(item.sourceGroups || []).map((id: string) => ({ id, nickname: `群组${id}`, avatar: "/placeholder.svg" })) + ], + creator: item.creator || "系统", + itemCount: 0, + lastUpdated: item.updateTime, + enabled: item.isEnabled === 1, + // 新增字段 + sourceFriends: item.sourceFriends || [], + sourceGroups: item.sourceGroups || [], + keywordInclude: item.keywordInclude || [], + keywordExclude: item.keywordExclude || [], + isEnabled: item.isEnabled, + aiPrompt: item.aiPrompt || '', + timeEnabled: item.timeEnabled, + timeStart: item.timeStart || '', + timeEnd: item.timeEnd || '', + status: item.status, + createTime: item.createTime, + updateTime: item.updateTime, + sourceType: item.sourceType + } + return transformedItem + }) + setLibraries(transformedLibraries) + } else { + showToast(response.msg || "获取内容库列表失败", "error") + } + } catch (error: any) { + console.error("获取内容库列表失败:", error) + showToast(error?.message || "请检查网络连接", "error") + } finally { + setLoading(false) + } + }, [searchQuery, activeTab]) + + // 首次加载和搜索条件变化时获取列表 + useEffect(() => { + fetchLibraries() + }, [searchQuery, activeTab, fetchLibraries]) const handleCreateNew = () => { - // 模拟创建新内容库 - const newId = Date.now().toString() - const newLibrary = { - id: newId, - name: "新内容库", - source: "friends" as const, - targetAudience: [], - creator: "当前用户", - itemCount: 0, - lastUpdated: new Date().toISOString(), - enabled: true, - } - setLibraries([newLibrary, ...libraries]) - router.push(`/content/${newId}`) + router.push('/content/new') } const handleEdit = (id: string) => { - router.push(`/content/${id}`) + router.push(`/content/${id}/edit`) } - const handleDelete = (id: string) => { - // 实现删除功能 - setLibraries(libraries.filter((lib) => lib.id !== id)) + const handleDelete = async (id: string) => { + try { + const response = await api.delete(`/v1/content/library/delete?id=${id}`) + if (response.code === 200) { + showToast("删除成功", "success") + fetchLibraries() + } else { + showToast(response.msg || "删除失败", "error") + } + } catch (error: any) { + console.error("删除内容库失败:", error) + showToast(error?.message || "请检查网络连接", "error") + } } const handleViewMaterials = (id: string) => { router.push(`/content/${id}/materials`) } + const handleSearch = () => { + fetchLibraries() + } + + const handleRefresh = () => { + fetchLibraries() + } + const filteredLibraries = libraries.filter( (library) => - (activeTab === "all" || library.source === activeTab) && - (library.name.toLowerCase().includes(searchQuery.toLowerCase()) || - library.targetAudience.some((target) => target.nickname.toLowerCase().includes(searchQuery.toLowerCase()))), + library.name.toLowerCase().includes(searchQuery.toLowerCase()) || + library.targetAudience.some((target) => target.nickname.toLowerCase().includes(searchQuery.toLowerCase())) ) return ( @@ -105,7 +172,7 @@ export default function ContentLibraryPage() {

内容库

- @@ -122,14 +189,20 @@ export default function ContentLibraryPage() { placeholder="搜索内容库..." value={searchQuery} onChange={(e) => setSearchQuery(e.target.value)} + onKeyDown={(e) => e.key === 'Enter' && handleSearch()} className="pl-9" /> - - @@ -142,46 +215,50 @@ export default function ContentLibraryPage() {
- {filteredLibraries.map((library) => ( - -
-
-
-

{library.name}

- - {library.enabled ? "已启用" : "已停用"} - -
-
-
- 来源: -
- {library.targetAudience.slice(0, 3).map((target) => ( - {target.nickname} - ))} - {library.targetAudience.length > 3 && ( - - +{library.targetAudience.length - 3} - - )} -
+ {loading ? ( +
+ +
+ ) : filteredLibraries.length === 0 ? ( +
+
+

暂无数据

+ +
+
+ ) : ( + filteredLibraries.map((library) => ( + +
+
+
+

{library.name}

+ + 已启用 + +
+
+
+ 来源: +
+
+
创建人:{library.creator}
+
内容数量:{library.itemCount}
+
更新时间:{new Date(library.updateTime).toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit' + })}
-
创建人:{library.creator}
-
内容数量:{library.itemCount}
-
更新时间:{library.lastUpdated}
-
-
- @@ -201,9 +278,9 @@ export default function ContentLibraryPage() {
-
- - ))} + + )) + )}
diff --git a/Cunkebao/components/ui/date-range-picker.tsx b/Cunkebao/components/ui/date-range-picker.tsx index ca97bb9e..a27264f4 100644 --- a/Cunkebao/components/ui/date-range-picker.tsx +++ b/Cunkebao/components/ui/date-range-picker.tsx @@ -49,6 +49,8 @@ export function DateRangePicker({ className, value, onChange }: DateRangePickerP onSelect={onChange} numberOfMonths={2} locale={zhCN} + disabled={(date) => date < new Date()} + showOutsideDays={false} /> diff --git a/Server/application/api/controller/AccountController.php b/Server/application/api/controller/AccountController.php index 44a09056..d46eff13 100644 --- a/Server/application/api/controller/AccountController.php +++ b/Server/application/api/controller/AccountController.php @@ -104,9 +104,9 @@ class AccountController extends BaseController if (empty($userName)) { return errorJson('用户名不能为空'); } - if (!preg_match('/^[a-zA-Z][a-zA-Z0-9]{5,9}$/', $userName)) { - return errorJson('用户名必须以字母开头,只能包含字母和数字,长度6-10位'); - } + // if (!preg_match('/^[a-zA-Z][a-zA-Z0-9]{5,9}$/', $userName)) { + // return errorJson('用户名必须以字母开头,只能包含字母和数字,长度6-10位'); + // } if (empty($password)) { return errorJson('密码不能为空'); } diff --git a/Server/application/common/service/AuthService.php b/Server/application/common/service/AuthService.php index fcd52e57..9b062059 100644 --- a/Server/application/common/service/AuthService.php +++ b/Server/application/common/service/AuthService.php @@ -168,6 +168,7 @@ class AuthService // 尝试从缓存获取授权信息 $authorization = Cache::get($cacheKey); + $authorization = 'xwz8Uh2doczeTCqSvakElfZMPn-jRce5WTnKTz3ljqpa63PnUOy5beT3TDhxnGNsROofYzpUphfhraxPrQfXvSuMyxFj_vrMUenzptj6hdG8Y4h1NrPXHFUr5Rlw-cIq0uyZZhjYp6xDTLg-IipgyAvBPdJM0vIgbizbo-agd8_Ubwbl0EOPqrMscYdsGrnv9_Lbr_B4-tHMNMa6yerb6kP6rzx8KQ4mJ6Cr5OmPX2WAmFkYykS3p0erWtb9PGHcxgaI1SVkEF4vH2H_iSOxfz5v27xd4HFE63IA5ZtDHQBNeiR0avST36UJSTZz3vjta9FDsw'; // 如果缓存中没有或已过期,则重新获取 if (empty($authorization)) { diff --git a/Server/application/cunkebao/config/route.php b/Server/application/cunkebao/config/route.php index 8b42cd95..f2c0ba54 100644 --- a/Server/application/cunkebao/config/route.php +++ b/Server/application/cunkebao/config/route.php @@ -57,4 +57,12 @@ Route::group('v1/', function () { Route::get('detail', 'app\\cunkebao\\controller\\WorkbenchController@detail'); // 获取工作台详情 Route::post('update', 'app\\cunkebao\\controller\\WorkbenchController@update'); // 更新工作台 }); + + // 内容库相关 + Route::group('content/library', function () { + Route::post('create', 'app\\cunkebao\\controller\\ContentLibraryController@create'); // 创建内容库 + Route::get('list', 'app\\cunkebao\\controller\\ContentLibraryController@getList'); // 获取内容库列表 + Route::post('update', 'app\\cunkebao\\controller\\ContentLibraryController@update'); // 更新内容库 + Route::delete('delete', 'app\\cunkebao\\controller\\ContentLibraryController@delete'); // 删除内容库 + }); })->middleware(['jwt']); \ No newline at end of file diff --git a/Server/application/cunkebao/controller/ContentLibraryController.php b/Server/application/cunkebao/controller/ContentLibraryController.php index b4676ceb..0ee65de2 100644 --- a/Server/application/cunkebao/controller/ContentLibraryController.php +++ b/Server/application/cunkebao/controller/ContentLibraryController.php @@ -21,9 +21,11 @@ class ContentLibraryController extends Controller $page = $this->request->param('page', 1); $limit = $this->request->param('limit', 10); $keyword = $this->request->param('keyword', ''); + $sourceType = $this->request->param('sourceType', ''); // 新增:来源类型,1=好友,2=群 $where = [ - ['userId', '=', $this->request->userInfo['id']] + ['userId', '=', $this->request->userInfo['id']], + ['isDel', '=', 0] // 只查询未删除的记录 ]; // 添加名称模糊搜索 @@ -31,12 +33,34 @@ class ContentLibraryController extends Controller $where[] = ['name', 'like', '%' . $keyword . '%']; } + // 添加名称模糊搜索 + if (!empty($sourceType)) { + $where[] = ['sourceType', '=', $sourceType]; + } + + + $list = ContentLibrary::where($where) - ->field('id,name,description,createTime,updateTime') + ->field('id,name,sourceFriends,sourceGroups,keywordInclude,keywordExclude,aiEnabled,aiPrompt,timeEnabled,timeStart,timeEnd,status,sourceType,userId,createTime,updateTime') + ->with(['user' => function($query) { + $query->field('id,username'); + }]) ->order('id', 'desc') ->page($page, $limit) ->select(); + // 处理JSON字段 + foreach ($list as &$item) { + $item['sourceFriends'] = json_decode($item['sourceFriends'] ?: '[]', true); + $item['sourceGroups'] = json_decode($item['sourceGroups'] ?: '[]', true); + $item['keywordInclude'] = json_decode($item['keywordInclude'] ?: '[]', true); + $item['keywordExclude'] = json_decode($item['keywordExclude'] ?: '[]', true); + // 添加创建人名称 + $item['creatorName'] = $item['user']['username'] ?? ''; + unset($item['user']); // 移除关联数据 + } + unset($item); + $total = ContentLibrary::where($where)->count(); return json([ @@ -46,38 +70,44 @@ class ContentLibraryController extends Controller 'list' => $list, 'total' => $total, 'page' => $page, - 'limit' => $limit ] ]); } /** * 获取内容库详情 - * @param int $id 内容库ID * @return \think\response\Json */ - public function detail($id) + public function detail() { + $id = $this->request->param('id', 0); if (empty($id)) { return json(['code' => 400, 'msg' => '参数错误']); } $library = ContentLibrary::where([ ['id', '=', $id], - ['userId', '=', $this->request->userInfo['id']] + ['userId', '=', $this->request->userInfo['id']], + ['isDel', '=', 0] // 只查询未删除的记录 ]) - ->field('id,name,description,createTime,updateTime') + ->field('id,name,sourceFriends,sourceGroups,keywordInclude,keywordExclude,aiEnabled,aiPrompt,timeEnabled,timeStart,timeEnd,status,userId,companyId,createTime,updateTime') ->find(); if (empty($library)) { return json(['code' => 404, 'msg' => '内容库不存在']); } - // 获取内容项目 - $items = ContentItem::where('libraryId', $id)->select(); - $library['items'] = $items; + // 处理JSON字段转数组 + $library['sourceFriends'] = json_decode($library['sourceFriends'] ?: '[]', true); + $library['sourceGroups'] = json_decode($library['sourceGroups'] ?: '[]', true); + $library['keywordInclude'] = json_decode($library['keywordInclude'] ?: '[]', true); + $library['keywordExclude'] = json_decode($library['keywordExclude'] ?: '[]', true); - return json(['code' => 200, 'msg' => '获取成功', 'data' => $library]); + return json([ + 'code' => 200, + 'msg' => '获取成功', + 'data' => $library + ]); } /** @@ -93,31 +123,52 @@ class ContentLibraryController extends Controller // 获取请求参数 $param = $this->request->post(); - // 简单验证 + // 验证参数 if (empty($param['name'])) { return json(['code' => 400, 'msg' => '内容库名称不能为空']); } + // 检查内容库名称是否已存在 + $exists = ContentLibrary::where('name', $param['name'])->find(); + if ($exists) { + return json(['code' => 400, 'msg' => '内容库名称已存在']); + } + Db::startTrans(); try { + // 构建数据 + $data = [ + 'name' => $param['name'], + // 数据来源配置 + 'sourceFriends' => isset($param['friends']) ? json_encode($param['friends']) : '[]', // 选择的微信好友 + 'sourceGroups' => isset($param['groups']) ? json_encode($param['groups']) : '[]', // 选择的微信群 + // 关键词配置 + 'keywordInclude' => isset($param['keywordInclude']) ? json_encode($param['keywordInclude']) : '[]', // 包含的关键词 + 'keywordExclude' => isset($param['keywordExclude']) ? json_encode($param['keywordExclude']) : '[]', // 排除的关键词 + // AI配置 + 'aiEnabled' => isset($param['aiEnabled']) ? $param['aiEnabled'] : 0, // 是否启用AI + 'aiPrompt' => isset($param['aiPrompt']) ? $param['aiPrompt'] : '', // AI提示词 + // 时间配置 + 'timeEnabled' => isset($param['timeEnabled']) ? $param['timeEnabled'] : 0, // 是否启用时间限制 + 'timeStart' => isset($param['startTime']) ? strtotime($param['startTime']) : 0, // 开始时间(转换为时间戳) + 'timeEnd' => isset($param['endTime']) ? strtotime($param['endTime']) : 0, // 结束时间(转换为时间戳) + // 来源类型 + 'sourceType' => isset($param['sourceType']) ? $param['sourceType'] : 0, // 1=好友,2=群,3=好友和群 + // 基础信息 + 'status' => isset($param['status']) ? $param['status'] : 0, // 状态:0=禁用,1=启用 + 'userId' => $this->request->userInfo['id'], + 'companyId' => $this->request->userInfo['companyId'], + 'createTime' => time(), + 'updateTime' => time() + ]; + // 创建内容库 $library = new ContentLibrary; - $library->name = $param['name']; - $library->description = isset($param['description']) ? $param['description'] : ''; - $library->userId = $this->request->userInfo['id']; - $library->companyId = $this->request->userInfo['companyId']; - $library->save(); + $result = $library->save($data); - // 如果有内容项目,也一并创建 - if (!empty($param['items']) && is_array($param['items'])) { - foreach ($param['items'] as $item) { - $contentItem = new ContentItem; - $contentItem->libraryId = $library->id; - $contentItem->type = $item['type']; - $contentItem->title = $item['title'] ?? ''; - $contentItem->contentData = $item['contentData']; - $contentItem->save(); - } + if (!$result) { + Db::rollback(); + return json(['code' => 500, 'msg' => '创建内容库失败']); } Db::commit(); @@ -177,36 +228,33 @@ class ContentLibraryController extends Controller /** * 删除内容库 - * @param int $id 内容库ID * @return \think\response\Json */ - public function delete($id) + public function delete() { + $id = $this->request->param('id', 0); if (empty($id)) { return json(['code' => 400, 'msg' => '参数错误']); } $library = ContentLibrary::where([ ['id', '=', $id], - ['userId', '=', $this->request->userInfo['id']] + ['userId', '=', $this->request->userInfo['id']], + ['isDel', '=', 0] // 只删除未删除的记录 ])->find(); - if (!$library) { + if (empty($library)) { return json(['code' => 404, 'msg' => '内容库不存在']); } - Db::startTrans(); try { - // 删除相关内容项目 - ContentItem::where('libraryId', $id)->delete(); - - // 删除内容库 - $library->delete(); + // 软删除 + $library->isDel = 1; + $library->deleteTime = time(); + $library->save(); - Db::commit(); return json(['code' => 200, 'msg' => '删除成功']); } catch (\Exception $e) { - Db::rollback(); return json(['code' => 500, 'msg' => '删除失败:' . $e->getMessage()]); } } diff --git a/Server/application/store/controller/BaseController.php b/Server/application/store/controller/BaseController.php index 4e8472c2..400bc660 100644 --- a/Server/application/store/controller/BaseController.php +++ b/Server/application/store/controller/BaseController.php @@ -35,10 +35,10 @@ class BaseController extends Api $device = Cache::get($cacheKey); // 如果缓存不存在,则从数据库获取 if (!$device) { - $device = Db::table('ck_device_user') + $device = Db::name('device_user') ->alias('du') - ->join('s2_device d', 'd.id = du.deviceId','left') - ->join('s2_wechat_account wa', 'd.id = wa.currentDeviceId','left') + ->join(['s2_device' => 'd'], 'd.id = du.deviceId','left') + ->join(['s2_wechat_account' => 'wa'], 'd.id = wa.currentDeviceId','left') ->where([ 'du.userId' => $this->userInfo['id'], 'du.companyId' => $this->userInfo['companyId'] diff --git a/Server/application/store/controller/CustomerController.php b/Server/application/store/controller/CustomerController.php index 371627bf..540b0105 100644 --- a/Server/application/store/controller/CustomerController.php +++ b/Server/application/store/controller/CustomerController.php @@ -54,11 +54,11 @@ class CustomerController extends Api // } // 构建查询 - $query = Db::table('ck_device_user') + $query = Db::name('device_user') ->alias('du') - ->join('s2_device d', 'd.id = du.deviceId','left') - ->join('s2_wechat_account wa', 'wa.imei = d.imei','left') - ->join('s2_wechat_friend wf', 'wf.ownerWechatId = wa.wechatId','left') + ->join(['s2_device' => 'd'], 'd.id = du.deviceId','left') + ->join(['s2_wechat_account' => 'wa'], 'wa.imei = d.imei','left') + ->join(['s2_wechat_friend' => 'wf'], 'wf.ownerWechatId = wa.wechatId','left') ->where($where) ->field('d.id as deviceId,d.imei,wf.*') ->group('wf.wechatId'); // 防止重复数据 diff --git a/Server/application/store/model/WechatFriendModel.php b/Server/application/store/model/WechatFriendModel.php index 4630713f..17eb02b1 100644 --- a/Server/application/store/model/WechatFriendModel.php +++ b/Server/application/store/model/WechatFriendModel.php @@ -6,6 +6,6 @@ use think\Model; class WechatFriendModel extends Model { - protected $table = 'ck_wechat_friend'; + protected $table = 's2_wechat_friend'; } \ No newline at end of file diff --git a/Server/application/store/model/WechatMessageModel.php b/Server/application/store/model/WechatMessageModel.php index c6e9cc87..f9c9061b 100644 --- a/Server/application/store/model/WechatMessageModel.php +++ b/Server/application/store/model/WechatMessageModel.php @@ -6,6 +6,6 @@ use think\Model; class WechatMessageModel extends Model { - protected $table = 'ck_wechat_message'; + protected $table = 's2_wechat_message'; } \ No newline at end of file diff --git a/Store_vue/api/config/index.js b/Store_vue/api/config/index.js new file mode 100644 index 00000000..1b2a84bf --- /dev/null +++ b/Store_vue/api/config/index.js @@ -0,0 +1,91 @@ +// API配置文件 + +// 基础配置 +export const BASE_URL = 'http://yi.cn' +//export const BASE_URL = 'https://ckbapi.quwanzhi.com' + +// 获取请求头 +const getHeaders = (options = {}) => { + const token = uni.getStorageSync('token'); + return { + 'content-type': 'application/json', + ...(token ? { 'Authorization': `Bearer ${token}` } : {}), + ...options.header + }; +}; + +// 请求配置 +export const request = (options) => { + return new Promise((resolve, reject) => { + const requestTask = uni.request({ + url: BASE_URL + options.url, + method: options.method || 'GET', + data: options.data, + header: getHeaders(options), + success: (res) => { + if (res.statusCode === 200) { + resolve(res.data) + } else if (res.statusCode === 401) { + // token过期或无效 + uni.removeStorageSync('token'); + uni.removeStorageSync('member'); + uni.removeStorageSync('token_expired'); + uni.showToast({ + title: '登录已过期,请重新登录', + icon: 'none' + }); + setTimeout(() => { + uni.reLaunch({ + url: '/pages/login/index' + }); + }, 1500); + reject(res); + } else { + handleError(res) + reject(res) + } + }, + fail: (err) => { + handleError(err) + reject(err) + } + }) + + // 超时处理 + setTimeout(() => { + if (requestTask) { + requestTask.abort() + handleError({ message: '请求超时,请稍后重试' }) + reject({ message: '请求超时,请稍后重试' }) + } + }, options.timeout || 30000) + }) +} + +// 错误处理函数 +const handleError = (error) => { + let message = '网络请求失败,请稍后重试' + + if (error.errMsg && error.errMsg.includes('解析失败')) { + message = '网页解析失败,可能是不支持的网页类型,请稍后重试' + } + + console.log(message) + // uni.showToast({ + // title: message, + // icon: 'none', + // duration: 2000 + // }) +} + +// 请求重试函数 +export const requestWithRetry = async (options, maxRetries = 3) => { + for (let i = 0; i < maxRetries; i++) { + try { + return await request(options) + } catch (error) { + if (i === maxRetries - 1) throw error + await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1))) + } + } +} \ No newline at end of file diff --git a/Store_vue/api/modules/auth.js b/Store_vue/api/modules/auth.js new file mode 100644 index 00000000..b6855228 --- /dev/null +++ b/Store_vue/api/modules/auth.js @@ -0,0 +1,17 @@ +import { request, requestWithRetry } from '../config' + +// 认证相关API +export const authApi = { + // 用户登录 + login: (account, password) => { + return request({ + url: '/v1/auth/login', + method: 'POST', + data: { + account: account, + password: password, + typeId: 2 // 固定为2 + } + }) + } +} \ No newline at end of file diff --git a/Store_vue/api/modules/example.js b/Store_vue/api/modules/example.js new file mode 100644 index 00000000..29e08480 --- /dev/null +++ b/Store_vue/api/modules/example.js @@ -0,0 +1,31 @@ +import { request, requestWithRetry } from '../config' + +// 示例API +export const exampleApi = { + // 普通请求示例 + getData: (params) => { + return request({ + url: '/api/getData', + method: 'GET', + data: params + }) + }, + + // 使用重试机制的请求示例 + getDataWithRetry: (params) => { + return requestWithRetry({ + url: '/api/getData', + method: 'GET', + data: params + }) + }, + + // POST请求示例 + postData: (data) => { + return request({ + url: '/api/postData', + method: 'POST', + data + }) + } +} \ No newline at end of file diff --git a/Store_vue/api/modules/traffic.js b/Store_vue/api/modules/traffic.js new file mode 100644 index 00000000..8d37c18f --- /dev/null +++ b/Store_vue/api/modules/traffic.js @@ -0,0 +1,31 @@ +import { request, requestWithRetry } from '../config' + +// 流量相关API +export const trafficApi = { + // 获取流量套餐列表 + getFlowPackages: () => { + return requestWithRetry({ + url: '/v1/store/flow-packages', + method: 'GET' + }) + }, + + // 获取本月流量使用情况 + getRemainingFlow: () => { + return requestWithRetry({ + url: '/v1/store/flow-packages/remaining-flow', + method: 'GET' + }) + }, + + // 创建流量套餐订单 + createOrder: (packageId) => { + return request({ + url: '/v1/store/flow-packages/order', + method: 'POST', + data: { + packageId + } + }) + } +} \ No newline at end of file diff --git a/Store_vue/api/utils/auth.js b/Store_vue/api/utils/auth.js new file mode 100644 index 00000000..06201804 --- /dev/null +++ b/Store_vue/api/utils/auth.js @@ -0,0 +1,48 @@ +/** + * token验证与跳转工具函数 + */ + +// 检查token是否存在 +export const hasToken = () => { + return !!uni.getStorageSync('token'); +}; + +// 检查token是否过期 +export const isTokenExpired = () => { + const expiredTime = uni.getStorageSync('token_expired'); + if (!expiredTime) return true; + + // 将当前时间转换为秒级时间戳,确保与expiredTime单位一致 + const currentTimeInSeconds = Math.floor(Date.now() / 1000); + + return currentTimeInSeconds >= expiredTime; +}; + +// 检查是否有有效token +export const hasValidToken = () => { + return hasToken() && !isTokenExpired(); +}; + +// 清除token信息 +export const clearToken = () => { + uni.removeStorageSync('token'); + uni.removeStorageSync('member'); + uni.removeStorageSync('token_expired'); +}; + +// 跳转到登录页面 +export const redirectToLogin = () => { + const currentPage = getCurrentPages().pop(); + if (currentPage && currentPage.route !== 'pages/login/index') { + uni.reLaunch({ + url: '/pages/login/index' + }); + } +}; + +// 跳转到聊天页面 +export const redirectToChat = () => { + uni.reLaunch({ + url: '/pages/chat/index' + }); +}; \ No newline at end of file