first commit
This commit is contained in:
541
pages/Chat/Chat.css
Normal file
541
pages/Chat/Chat.css
Normal file
@@ -0,0 +1,541 @@
|
||||
.chat-page {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 笼罩层样式 */
|
||||
.mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 998;
|
||||
animation: fadeIn 1s ease;
|
||||
}
|
||||
|
||||
/* 自定义淡入动画 */
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* ================会话侧边栏样式================== */
|
||||
.chat-sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: calc(100% - 100rpx);
|
||||
max-width: 680rpx;
|
||||
height: 100%;
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.5s ease;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.chat-sidebar.sidebar-show {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
|
||||
/* =============================新建会话弹窗样式================== */
|
||||
.ncd-overlay {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(193, 193, 193, 0.4);
|
||||
z-index: 1000;
|
||||
/* 毛玻璃核心代码 👇 */
|
||||
backdrop-filter: blur(8px);
|
||||
-webkit-backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
.ncd-card {
|
||||
width: 90%;
|
||||
height: auto;
|
||||
background-color: #ffffff;
|
||||
border-radius: 50rpx;
|
||||
padding: 50rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ncd-header {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ncd-title-eng {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
background-color: #000;
|
||||
padding: 5rpx 8rpx;
|
||||
border-radius: 10rpx;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ncd-title-eng text {
|
||||
color: #ffffff;
|
||||
font-weight: bold;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
|
||||
.ncd-title-zh {
|
||||
margin-left: 10rpx;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
height: 0;
|
||||
font-size: 38rpx;
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.ncd-header uni-icons {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.ncd-options {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 10rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ncd-option {
|
||||
width: 100%;
|
||||
background: #f8fafc;
|
||||
padding: 50rpx 30rpx;
|
||||
display: flex;
|
||||
border-radius: 32rpx;
|
||||
margin-top: 20rpx;
|
||||
box-sizing: border-box;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ncd-normal {
|
||||
border: 1rpx solid #e8edf3;
|
||||
}
|
||||
|
||||
.ncd-opt-icon {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 30rpx;
|
||||
padding: 10rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.ncd-normal .ncd-opt-icon {
|
||||
background-color: #e2e8f0;
|
||||
}
|
||||
|
||||
|
||||
.ncd-opt-title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: 0rpx 16rpx;
|
||||
box-sizing: border-box;
|
||||
justify-content: center;
|
||||
flex: 1;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.ncd-opt-title-1 {
|
||||
font-size: 30rpx;
|
||||
font-weight: bold;
|
||||
color: #000000;
|
||||
}
|
||||
|
||||
.ncd-opt-title-2 {
|
||||
font-size: 26rpx;
|
||||
color: #666666;
|
||||
}
|
||||
|
||||
.arrow-right-style {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
font-weight: bold !important;
|
||||
font-size: 30rpx !important;
|
||||
color: #666666 !important;
|
||||
}
|
||||
|
||||
.ncd-intelligence {
|
||||
background: linear-gradient(to right, #f5f3ff, #ede9fe);
|
||||
border: 1rpx solid #c4b5fd;
|
||||
|
||||
}
|
||||
|
||||
.ncd-intelligence .ncd-opt-icon {
|
||||
background-color: #8b5cf6;
|
||||
}
|
||||
|
||||
/* ==========新建聊天列表相关====== */
|
||||
.close-option-card {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.close-option-card .iconfont {
|
||||
color: #ff0000 !important;
|
||||
}
|
||||
|
||||
.chat-option-list {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.chat-option {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
margin: 10rpx 0;
|
||||
box-sizing: border-box;
|
||||
|
||||
}
|
||||
|
||||
.chat-option .iconfont {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.option-name {
|
||||
font-size: 18px;
|
||||
font-weight: 400;
|
||||
margin-left: 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* ================聊天容器============== */
|
||||
.chat-wrapper {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* ================聊天顶部样式============== */
|
||||
.chat-hearder {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
padding: 20rpx 10rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
border-bottom: 1rpx solid #ede9fe;
|
||||
}
|
||||
|
||||
.chat-btn-group {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.head-btn {
|
||||
padding: 10rpx;
|
||||
border: 1rpx solid #ede9fe;
|
||||
border-radius: 20rpx;
|
||||
margin: 0 10rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chat-btn-group .head-btn:nth-child(1) .iconfont {
|
||||
color: #c4b5fd;
|
||||
}
|
||||
|
||||
.chat-btn-group .head-btn:nth-child(2) .iconfont {
|
||||
color: cornflowerblue;
|
||||
}
|
||||
|
||||
.head-btn .iconfont {
|
||||
font-size: 50rpx;
|
||||
}
|
||||
|
||||
.log-out {
|
||||
/* background: #ffd4d4; */
|
||||
}
|
||||
|
||||
.log-out .iconfont {
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
/* ==========聊天主体部分========== */
|
||||
.main-chat {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
height: 0;
|
||||
width: 100%;
|
||||
flex-direction: column;
|
||||
|
||||
}
|
||||
|
||||
/* ==========聊天对话展示部分========== */
|
||||
.chat-messages {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
height: 0;
|
||||
width: 100%;
|
||||
padding: 0rpx 20rpx;
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.chat-message {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
margin: 10rpx 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.message-user {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.chat-avatar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
box-sizing: border-box;
|
||||
border-radius: 10rpx;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.friend-avatar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 20rpx;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.chat-avatar-user {
|
||||
margin-right: 0;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
|
||||
.chat-content {
|
||||
width: auto;
|
||||
max-width: calc(100% - 100rpx);
|
||||
height: auto;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
border: 1rpx solid #999;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.chat-content-user {
|
||||
background-color: #c4b5fd;
|
||||
color: #fff;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
/* 图片消息 */
|
||||
.message-image {
|
||||
max-width: 300rpx;
|
||||
border-radius: 8rpx;
|
||||
margin: 6rpx 0;
|
||||
}
|
||||
|
||||
/* 文件列表 */
|
||||
.message-file-list {
|
||||
margin: 8rpx 0;
|
||||
}
|
||||
|
||||
/* 文件项 */
|
||||
.file-item {
|
||||
margin: 6rpx 0;
|
||||
}
|
||||
|
||||
/* 文件样式 */
|
||||
.message-file {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12rpx 16rpx;
|
||||
background: #f7f8fa;
|
||||
border-radius: 8rpx;
|
||||
max-width: 350rpx;
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
font-size: 32rpx;
|
||||
margin-right: 12rpx;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
flex: 1;
|
||||
font-size: 26rpx;
|
||||
color: #333;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.file-size {
|
||||
font-size: 22rpx;
|
||||
color: #999;
|
||||
margin-left: 8rpx;
|
||||
}
|
||||
|
||||
|
||||
/* ==========聊天输入容器部分========== */
|
||||
.chat-interactive-container {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
width: 100%;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.chat-interactive-group {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.chat-interactive-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1rpx solid #000000;
|
||||
border-radius: 10rpx;
|
||||
padding: 10rpx 16rpx;
|
||||
margin-right: 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.chat-input-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
border: 1rpx solid #c4b5fd;
|
||||
border-radius: 30rpx;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.message-input {
|
||||
flex: 1;
|
||||
max-height: 150rpx;
|
||||
min-height: 60rpx;
|
||||
overflow-y: auto;
|
||||
margin: 10rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.input-btn-group {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 16rpx;
|
||||
box-sizing: border-box;
|
||||
background: #ffd4d4;
|
||||
border: 1rpx solid #ff0000;
|
||||
border-radius: 20rpx;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
}
|
||||
|
||||
.input-btn-group .iconfont {
|
||||
color: #ff0000;
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
/* ai思考弹窗卡片 */
|
||||
.ai-card {
|
||||
width: auto;
|
||||
border: 1rpx solid #409eff;
|
||||
box-shadow: 0 0 20rpx #c4b5fd;
|
||||
}
|
||||
|
||||
/* 内容布局 */
|
||||
.thinking-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 加载点点动画 */
|
||||
.loading-dots {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.dot {
|
||||
width: 16rpx;
|
||||
height: 16rpx;
|
||||
margin: 6rpx;
|
||||
border-radius: 50%;
|
||||
background: #409eff;
|
||||
animation: dotBlink 1.2s infinite ease-in-out;
|
||||
}
|
||||
|
||||
/* 点点呼吸动画 */
|
||||
@keyframes dotBlink {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.3;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.dot:nth-child(2) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.dot:nth-child(3) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
/* 文字 */
|
||||
.thinking-text {
|
||||
margin: 20rpx 0;
|
||||
font-size: 22px;
|
||||
font-weight: 500;
|
||||
color: #409eff;
|
||||
}
|
||||
1135
pages/Chat/Chat.vue
Normal file
1135
pages/Chat/Chat.vue
Normal file
File diff suppressed because it is too large
Load Diff
395
pages/ContactPages/ContactPages.css
Normal file
395
pages/ContactPages/ContactPages.css
Normal file
@@ -0,0 +1,395 @@
|
||||
.tabbar-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.content-swiper {
|
||||
width: 100%;
|
||||
height: calc(100% - 100rpx);
|
||||
}
|
||||
|
||||
.tabpage-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tabpage-containner {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tabpage-header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.tabpage-header .iconfont {
|
||||
font-size: 50rpx;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.tabpage-title {
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.tabpage-body {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
}
|
||||
|
||||
/* 新建群聊 */
|
||||
.group-info {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.group-name {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.group-name .input {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
border: 1rpx solid #007aff;
|
||||
border-radius: 30rpx;
|
||||
}
|
||||
|
||||
.group-member-wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
background-color: rgba(16, 185, 129, .05);
|
||||
border: 1rpx solid rgba(16, 185, 129, .2);
|
||||
border-radius: 30rpx;
|
||||
margin: 10rpx 0;
|
||||
}
|
||||
|
||||
.group-member-wrapper .member-title {
|
||||
color: #059669;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
.group-ismember-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-top: 10rpx;
|
||||
box-sizing: border-box;
|
||||
max-height: 200rpx;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.group-ismember-info {
|
||||
padding: 10rpx 16rpx;
|
||||
box-sizing: border-box;
|
||||
border: 1rpx solid rgba(16, 185, 129, 0.8);
|
||||
background-color: #fff;
|
||||
border-radius: 30rpx;
|
||||
margin: 5rpx 10rpx;
|
||||
}
|
||||
|
||||
.group-ismember-name {
|
||||
font-size: 22rpx;
|
||||
color: #059669;
|
||||
}
|
||||
|
||||
.create-group-btn {
|
||||
width: 100%;
|
||||
background: #007aff;
|
||||
border-radius: 30rpx;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 10rpx;
|
||||
}
|
||||
|
||||
.group-member {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
padding: 10rpx;
|
||||
margin-top: 10rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.friend-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.friend-card {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
padding: 20rpx;
|
||||
margin: 10rpx 0;
|
||||
box-sizing: border-box;
|
||||
border: 1rpx solid #aaaaff;
|
||||
border-radius: 30rpx;
|
||||
}
|
||||
|
||||
.friend-card-left {
|
||||
flex-shrink: 0;
|
||||
margin-right: 10rpx;
|
||||
}
|
||||
|
||||
.friend-avatar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border-radius: 50%;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.friend-card-middle {
|
||||
flex: 1;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.friend-name {
|
||||
font-weight: 500;
|
||||
font-size: 32rpx;
|
||||
}
|
||||
|
||||
.friend-card-right {
|
||||
width: auto;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.friend-checkbox {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
border-radius: 10rpx;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 1rpx solid #3b86ff;
|
||||
}
|
||||
|
||||
/* 添加好友 */
|
||||
.search-warpper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-warpper .search-file-input {
|
||||
flex: 1;
|
||||
background-color: #f9fafb;
|
||||
border: 2rpx solid rgba(139, 195, 232, .2);
|
||||
padding: 20rpx;
|
||||
margin-right: 20rpx;
|
||||
height: auto;
|
||||
box-sizing: border-box;
|
||||
border-radius: 30rpx;
|
||||
}
|
||||
|
||||
.search-file-input.active {
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 0 15rpx #aaaaff;
|
||||
border-color: #007aff;
|
||||
}
|
||||
|
||||
.search-warpper .iconfont {
|
||||
flex-shrink: 0;
|
||||
font-size: 38rpx;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
background-color: #007aff;
|
||||
padding: 18rpx;
|
||||
box-sizing: border-box;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
|
||||
.search-result {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
background: #3b86ff;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 添加好友卡片 */
|
||||
.nickname-wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin: 20rpx 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.input-nickname {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
border: 1rpx solid #007aff;
|
||||
border-radius: 30rpx;
|
||||
margin: 20rpx 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.confirm-btn {
|
||||
background: #3b86ff;
|
||||
color: #fff;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
/* 视频会议 */
|
||||
.create-meeting-wrapper,
|
||||
.join-meeting-wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 20rpx 30rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.input-meeting {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
border: 1rpx solid #007aff;
|
||||
border-radius: 30rpx;
|
||||
margin: 10rpx 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
.meeting-btn {
|
||||
width: 100%;
|
||||
background: #007aff;
|
||||
margin: 10rpx 0;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.meeting-field {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.meeting-toggles {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.meeting-toggles-item {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
/* 好友申请 */
|
||||
.friend-option {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.agree-btn,
|
||||
.refuse-btn {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border: 1px solid #666;
|
||||
margin-left: 10rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
/* 通讯录 */
|
||||
.contact-list {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
padding: 10rpx;
|
||||
margin-top: 10rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
/* 底部导航 */
|
||||
.bottom-tabbar {
|
||||
width: 100%;
|
||||
height: 140rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
background: rgba(200, 200, 200, 0.1);
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
}
|
||||
|
||||
.tab-label {
|
||||
font-size: 28rpx;
|
||||
}
|
||||
778
pages/ContactPages/ContactPages.vue
Normal file
778
pages/ContactPages/ContactPages.vue
Normal file
@@ -0,0 +1,778 @@
|
||||
<template>
|
||||
<view class="status-bar"></view>
|
||||
<!-- 添加好友弹窗 -->
|
||||
<view class="popup-overlay" v-if="currentAddFriend">
|
||||
<view class="popup-card">
|
||||
<view class="close-popup-card" @click="closeAddFriendCard">
|
||||
<view class="iconfont icon-quxiao"></view>
|
||||
</view>
|
||||
<view class="popup-title">{{isAgreeBeFriend ? '通过好友申请' : '申请添加朋友'}}</view>
|
||||
<view class="friend-card">
|
||||
<view class="friend-card-left">
|
||||
<view class="friend-avatar">{{currentAddFriend.avatar}}</view>
|
||||
</view>
|
||||
<view class="friend-card-middle">
|
||||
<view class="friend-name">{{currentAddFriend.username}}</view>
|
||||
<view class="friend-label">{{currentAddFriend.sessionId}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="nickname-wrapper">
|
||||
<view>好友备注</view>
|
||||
<input class="input-nickname" placeholder="请输入好友名称" />
|
||||
</view>
|
||||
<view class="text-btn confirm-btn">{{isAgreeBeFriend ? '确认通过' : '发送申请'}}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="tabbar-page page-container">
|
||||
<swiper class="content-swiper" :current="currentTab" :duration="300" @change="onSwiperChange">
|
||||
|
||||
<swiper-item class="content-swiper-item">
|
||||
<view class="tabpage-containner">
|
||||
<view class="tabpage-header">
|
||||
<view class="iconfont icon-fanhui" @click="handleBack"></view>
|
||||
<view class="tabpage-title">通讯录</view>
|
||||
</view>
|
||||
<view class="tabpage-body">
|
||||
<scroll-view class="group-member" direction="vertical" scroll-y="true">
|
||||
<view class="friend-list">
|
||||
<view class="friend-card" @click="changeChatType(0)">
|
||||
<view class="friend-card-left">
|
||||
<view class="friend-avatar" style=" background-color: #38bdf8;">
|
||||
<view class="iconfont icon-Robot" style="font-size: 80rpx; color: #fff;">
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="friend-card-middle">
|
||||
<view class="friend-name">{{FriendAI.name}}</view>
|
||||
<view class="friend-label"></view>
|
||||
</view>
|
||||
<view class="friend-card-right">
|
||||
<uni-icons type="right"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<view class="friend-card" v-for="friend in FriendInfoList" :key="friend.sessionId" @click="changeChatType(friend.type,friend.sessionId)">
|
||||
<view class="friend-card-left">
|
||||
<!-- <view class="friend-avatar">{{friend.avatar}}</view> -->
|
||||
<!-- 显示头像图片 -->
|
||||
<image v-if="friend.avatar" :src="friend.avatar" class="friend-avatar"
|
||||
mode="aspectFill"></image>
|
||||
<!-- 如果没有头像,显示默认头像 -->
|
||||
<view v-else class="friend-avatar">
|
||||
<uni-icons type="person-filled" size="100rpx" color="#ffffff"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<view class="friend-card-middle">
|
||||
<view class="friend-name">{{friend.friendNickName}}</view>
|
||||
<view class="friend-label">{{friend.sessionId}}</view>
|
||||
</view>
|
||||
<view class="friend-card-right">
|
||||
<uni-icons type="right"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<view>群聊</view>
|
||||
<view class="solid-line"></view>
|
||||
<view class="friend-card" v-for="group in GroupList" :key="group.id" @click="changeChatType(group.type,group.id)">
|
||||
<view class="friend-card-left">
|
||||
<!-- 群聊只有默认头像 -->
|
||||
<view class="friend-avatar" style="background-color: #ffaaff;">
|
||||
<view class="iconfont icon-qunliao" style="font-size: 80rpx; color: #fff;">
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="friend-card-middle">
|
||||
<view class="friend-name">{{group.name}}</view>
|
||||
<view class="friend-label">{{group.createTime}}</view>
|
||||
</view>
|
||||
<view class="friend-card-right">
|
||||
<uni-icons type="right"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
|
||||
|
||||
<swiper-item class="content-swiper-item">
|
||||
<view class="tabpage-containner">
|
||||
<view class="tabpage-header">
|
||||
<view class="iconfont icon-fanhui" @click="handleBack"></view>
|
||||
<view class="tabpage-title">新建群聊</view>
|
||||
</view>
|
||||
<view class="tabpage-body">
|
||||
<view class="group-info">
|
||||
<view class="group-name">
|
||||
<view>群名称</view>
|
||||
<input class="input" placeholder="请输入群名称" v-model="createGroupName" />
|
||||
</view>
|
||||
<view class="group-member-wrapper">
|
||||
<view class="member-title">群成员</view>
|
||||
<view class="group-ismember-list">
|
||||
<view class="group-ismember-info" v-for="(item,index) in selectGroupMember"
|
||||
:key="index">
|
||||
<view class="group-ismember-name">{{item.friendNickName}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="create-group-btn" @click="createGroup">创建群聊</view>
|
||||
</view>
|
||||
<view class="solid-line"></view>
|
||||
<scroll-view class="group-member" direction="vertical" scroll-y="true">
|
||||
<view class="friend-list">
|
||||
<view class="friend-card" v-for="friend in FriendInfoList" :key="friend.receiver">
|
||||
<view class="friend-card-left">
|
||||
<image v-if="friend.avatar" :src="friend.avatar" class="friend-avatar"
|
||||
mode="aspectFill"></image>
|
||||
<!-- 如果没有头像,显示默认头像 -->
|
||||
<view v-else class="friend-avatar">
|
||||
<uni-icons type="person-filled" size="100rpx" color="#ffffff"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
<view class="friend-card-middle">
|
||||
<view class="friend-name">{{friend.friendNickName}}</view>
|
||||
<view class="friend-label">{{friend.email}}</view>
|
||||
</view>
|
||||
<view class="friend-card-right" @click="handlCreateEmember(friend.receiver)">
|
||||
<view class="friend-checkbox">
|
||||
<uni-icons type="checkmarkempty"
|
||||
v-if="selectGroupMember?.find(item => item.id === friend.receiver)"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
|
||||
<swiper-item class="content-swiper-item">
|
||||
<view class="tabpage-containner">
|
||||
<view class="tabpage-header">
|
||||
<view class="iconfont icon-fanhui" @click="handleBack"></view>
|
||||
<view class="tabpage-title">添加好友</view>
|
||||
</view>
|
||||
<view class="tabpage-body">
|
||||
<view class="search-warpper">
|
||||
<input class="search-file-input" :class="{active:isSearchFriend}"
|
||||
@focus="handleFriendSearchFocus" @blur="handleFriendSearchFocus"
|
||||
v-model="searchFriendName" />
|
||||
<view class="iconfont icon-sousuo" @click="searchFriend"></view>
|
||||
</view>
|
||||
<view v-if="!searchFriendName" class="search-tip">请输入用户名或邮箱进行搜索</view>
|
||||
<view v-else-if="searchResultList.length" class="search-result">
|
||||
<scroll-view class="group-member" direction="vertical" scroll-y="true">
|
||||
<view class="friend-list">
|
||||
<view class="friend-card" v-for="friend in searchResultList" :key="friend.id">
|
||||
<view class="friend-card-left">
|
||||
<view class="friend-avatar">{{friend.avatar}}</view>
|
||||
</view>
|
||||
<view class="friend-card-middle">
|
||||
<view class="friend-name">{{friend.nickname}}</view>
|
||||
<view class="friend-label">{{friend.email}}</view>
|
||||
</view>
|
||||
<view class="friend-card-right" @click="addNewFriend(friend.id)">
|
||||
<view class="add-btn text-btn">添加</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<view v-else class="no-result">未找到相关用户,请尝试其他关键词</view>
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
|
||||
<swiper-item class="content-swiper-item">
|
||||
<view class="tabpage-containner">
|
||||
<view class="tabpage-header">
|
||||
<view class="iconfont icon-fanhui" @click="handleBack"></view>
|
||||
<view class="tabpage-title">视频会议</view>
|
||||
</view>
|
||||
<view class="tabpage-body">
|
||||
<view class="join-meeting-wrapper">
|
||||
<view>加入会议</view>
|
||||
<input class="input-meeting" placeholder="请输入会议号" />
|
||||
<view class="meeting-btn text-btn">加入会议</view>
|
||||
</view>
|
||||
<view class="solid-line"></view>
|
||||
<view class="create-meeting-wrapper">
|
||||
<view>创建会议</view>
|
||||
<view class="meeting-field">
|
||||
<view class="meeting-label">会议主题</view>
|
||||
<input class="input-meeting" placeholder="例如:XXX" />
|
||||
</view>
|
||||
<view class="meeting-field">
|
||||
<view class="meeting-label">会议密码</view>
|
||||
<input class="input-meeting" placeholder="留空则无密码" />
|
||||
</view>
|
||||
<view class="meeting-field">
|
||||
<view class="meeting-label">邀请好友</view>
|
||||
<view class="friend-card">
|
||||
<view class="friend-card-left">
|
||||
<view class="friend-avatar">avatar</view>
|
||||
</view>
|
||||
<view class="friend-card-middle">
|
||||
<view class="friend-name">friend.nickname</view>
|
||||
<view class="friend-label">friend.email</view>
|
||||
</view>
|
||||
<view class="friend-card-right" @click="addNewFriend(friend.id)">
|
||||
<view class="add-btn text-btn">添加</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="meeting-toggles">
|
||||
<view class="meeting-toggles-item">
|
||||
<view class="meeting-toggles-icon icon-btn">
|
||||
<uni-icons type="mic-filled"></uni-icons>开启麦克风入会
|
||||
</view>
|
||||
<view class="meeting-toggles-switch" @click="togglesVoice">
|
||||
<view class="switch-box" :class="{active : isVoice}">
|
||||
<view class="switch-track"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="meeting-toggles-item">
|
||||
<view class="meeting-toggles-icon icon-btn">
|
||||
<uni-icons type="eye-filled"></uni-icons>开启摄像头入会
|
||||
</view>
|
||||
<view class="meeting-toggles-switch" @click="togglesCamera">
|
||||
<view class="switch-box" :class="{active : isCamera}">
|
||||
<view class="switch-track"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="meeting-btn text-btn">加入会议</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
|
||||
<swiper-item class="content-swiper-item">
|
||||
<view class="tabpage-containner">
|
||||
<view class="tabpage-header">
|
||||
<view class="iconfont icon-fanhui" @click="handleBack"></view>
|
||||
<view class="tabpage-title">好友申请</view>
|
||||
</view>
|
||||
<view class="tabpage-body">
|
||||
<scroll-view class="group-member" direction="vertical" scroll-y="true">
|
||||
<view class="friend-list">
|
||||
<view class="friend-card" v-for="friend in mockFriendDB" :key="friend.id">
|
||||
<view class="friend-card-left">
|
||||
<view class="friend-avatar">{{friend.avatar}}</view>
|
||||
</view>
|
||||
<view class="friend-card-middle">
|
||||
<view class="friend-name">{{friend.nickname}}</view>
|
||||
<view class="friend-label">{{friend.email}}</view>
|
||||
</view>
|
||||
<view class="friend-card-right">
|
||||
<view class="friend-option">
|
||||
<view class="refuse-btn text-btn" @click="refuseBeFriend(friend)">拒绝</view>
|
||||
<view class="agree-btn text-btn" @click="agreeBeFriend(friend)">同意</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
|
||||
|
||||
</swiper>
|
||||
<view class="bottom-tabbar">
|
||||
<view v-for="(tab, index) in tabList" :key="tab.name" @click="switchTab(index)" class="tab-item">
|
||||
<view class="tab-icon">{{tab.icon}}</view>
|
||||
<view class="tab-label">{{tab.label}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
onMounted,
|
||||
reactive,
|
||||
ref,
|
||||
watch
|
||||
} from 'vue';
|
||||
|
||||
import {
|
||||
getToken
|
||||
} from '@/utils/user-info.js'
|
||||
import {
|
||||
getChatFriend,
|
||||
getGroup
|
||||
} from '@/utils/friend-api.js'
|
||||
import {
|
||||
getUserInfo,
|
||||
getUserAvatar,
|
||||
searchUsers
|
||||
} from '@/utils/cloud-api.js'
|
||||
import {
|
||||
useFriendSocketStore
|
||||
} from '@/stores/friend-socket.js'
|
||||
|
||||
const friendSocketStore = useFriendSocketStore()
|
||||
// 连接实时通讯socket
|
||||
const handleFriendConnect = () => {
|
||||
if (friendSocketStore.isConnected) return
|
||||
if (!userToken.value || !UserId.value) {
|
||||
console.warn('Token或UserId未准备好');
|
||||
return
|
||||
}
|
||||
friendSocketStore.connect({
|
||||
token: userToken.value,
|
||||
UserId: UserId.value
|
||||
})
|
||||
}
|
||||
|
||||
// 返回聊天界面
|
||||
const handleBack = () => {
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
fail() {
|
||||
uni.reLaunch({
|
||||
url: '/pages/Chat/Chat'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 选择群聊或对话时,修改聊天类型并返回聊天页面
|
||||
const changeChatType = (type,sessionId='') =>{
|
||||
uni.setStorageSync('chatType', type)
|
||||
if(sessionId){
|
||||
uni.setStorageSync('currentSessionId',sessionId)
|
||||
}
|
||||
uni.reLaunch({
|
||||
url: '/pages/Chat/Chat'
|
||||
})
|
||||
}
|
||||
|
||||
// 底部Tab相关
|
||||
// 当前选中的tab索引 (0:新建群聊, 1:添加好友, 2:视频会议, 3:好友申请, 4:通讯录)
|
||||
const currentTab = ref(0)
|
||||
// Tab列表
|
||||
const tabList = reactive([{
|
||||
name: 'contacts',
|
||||
label: '通讯录',
|
||||
icon: '📞'
|
||||
},
|
||||
{
|
||||
name: 'createGroup',
|
||||
label: '新建群聊',
|
||||
icon: '👥'
|
||||
},
|
||||
{
|
||||
name: 'addFriend',
|
||||
label: '添加好友',
|
||||
icon: '➕'
|
||||
},
|
||||
{
|
||||
name: 'videoCall',
|
||||
label: '视频会议',
|
||||
icon: '📹'
|
||||
},
|
||||
{
|
||||
name: 'friendRequest',
|
||||
label: '好友申请',
|
||||
icon: '📨'
|
||||
}
|
||||
])
|
||||
|
||||
const onSwiperChange = (e) => {
|
||||
currentTab.value = e.detail.current
|
||||
}
|
||||
|
||||
const switchTab = (index) => {
|
||||
currentTab.value = index
|
||||
}
|
||||
|
||||
// 用户token
|
||||
const userToken = ref('')
|
||||
// 用户id
|
||||
const UserId = ref('')
|
||||
|
||||
// 通讯录相关
|
||||
|
||||
// AI好友
|
||||
const FriendAI = {
|
||||
|
||||
id: 'ai_robot',
|
||||
|
||||
name: '宇恒一号',
|
||||
|
||||
avatar: '',
|
||||
|
||||
type: 'ai',
|
||||
|
||||
description: 'AI智能助手',
|
||||
|
||||
lastMessage: null
|
||||
|
||||
}
|
||||
// 好友列表
|
||||
// const FriendList = ref([])
|
||||
// 好友消息列表(不包括头像)
|
||||
const FriendInfoList = ref([])
|
||||
// 获取用户好友列表
|
||||
const takeFriendList = async () => {
|
||||
try {
|
||||
console.log("开始获取好友列表");
|
||||
userToken.value = getToken();
|
||||
const userInfo = await getUserInfo(userToken.value)
|
||||
UserId.value = userInfo._id
|
||||
console.log("UserId.value:", UserId.value);
|
||||
const friendList = await getChatFriend(UserId.value)
|
||||
console.log("friendList:", friendList);
|
||||
if (friendList && friendList.length) {
|
||||
FriendInfoList.value = await takeUserAvatar(friendList)
|
||||
} else {
|
||||
FriendInfoList.value = []
|
||||
console.log("好友列表为空");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取好友列表失败:", error);
|
||||
FriendInfoList.value = []
|
||||
}
|
||||
}
|
||||
// 获取好友头像
|
||||
const takeUserAvatar = async (friendList) => {
|
||||
if (!friendList?.length) return []
|
||||
try {
|
||||
const friendIds = friendList.map(item => item.receiver)
|
||||
const friendAvatarList = await getUserAvatar(userToken.value, friendIds)
|
||||
// 创建map,用于快速查找
|
||||
const userMap = new Map(friendAvatarList.map(user => [user.user_id, user]) || [])
|
||||
// 合并数据
|
||||
return friendList.map(friend => {
|
||||
const userInfo = userMap.get(friend.receiver)
|
||||
return {
|
||||
...friend,
|
||||
avatar: userInfo?.avatar || null
|
||||
}
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('获取好友头像失败', err);
|
||||
return friendList
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await takeFriendList()
|
||||
await takeGroupList()
|
||||
handleFriendConnect()
|
||||
})
|
||||
// 群聊相关
|
||||
// 群聊列表
|
||||
const GroupList = ref([])
|
||||
// 获取用户群聊列表
|
||||
const takeGroupList = async () => {
|
||||
try {
|
||||
console.log("开始获取群聊列表");
|
||||
userToken.value = getToken();
|
||||
const userInfo = await getUserInfo(userToken.value)
|
||||
UserId.value = userInfo._id
|
||||
console.log("UserId.value:", UserId.value);
|
||||
GroupList.value = await getGroup(UserId.value)
|
||||
} catch (error) {
|
||||
console.error("获取群聊列表失败:", error);
|
||||
GroupList.value = []
|
||||
}
|
||||
}
|
||||
// 新建群聊相关
|
||||
const createGroupName = ref('')
|
||||
const createGroupData = ref()
|
||||
|
||||
// 确保 friendSocket 已连接
|
||||
const ensureFriendSocketConnected = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 已连接则直接返回
|
||||
if (friendSocketStore.isConnected) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
// 未连接,先建立连接
|
||||
if (!userToken.value || !UserId.value) {
|
||||
reject(new Error('Token或UserId未准备好'))
|
||||
return
|
||||
}
|
||||
console.log('friendSocket 未连接,正在建立连接...')
|
||||
friendSocketStore.connect({
|
||||
token: userToken.value,
|
||||
UserId: UserId.value
|
||||
})
|
||||
// 等待连接建立
|
||||
const checkInterval = setInterval(() => {
|
||||
if (friendSocketStore.isConnected) {
|
||||
clearInterval(checkInterval)
|
||||
console.log('friendSocket 连接已建立')
|
||||
resolve()
|
||||
}
|
||||
}, 100)
|
||||
// 最多等待10秒
|
||||
setTimeout(() => {
|
||||
clearInterval(checkInterval)
|
||||
if (!friendSocketStore.isConnected) {
|
||||
reject(new Error('连接超时'))
|
||||
}
|
||||
}, 10000)
|
||||
})
|
||||
}
|
||||
|
||||
const createGroup = async () => {
|
||||
try {
|
||||
//检查是否有群成员和群名称
|
||||
if(!createGroupName.value) {
|
||||
uni.showToast({
|
||||
title:'群名称不能为空',
|
||||
icon:'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
//检查是否有群成员和群名称
|
||||
if(selectGroupMember.value.length === 0) {
|
||||
uni.showToast({
|
||||
title:'请选择群成员',
|
||||
icon:'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
// 确保 socket 已连接
|
||||
await ensureFriendSocketConnected()
|
||||
|
||||
const memberIds = selectGroupMember.value.map(member => member.id)
|
||||
|
||||
createGroupData.value = {
|
||||
"version": "1.1",
|
||||
"body": {
|
||||
"command": 3,
|
||||
"userIds": memberIds,
|
||||
"message": "我来邀请你们了",
|
||||
"sender": UserId.value,
|
||||
"callBackMessage": true,
|
||||
"groupName": createGroupName.value,
|
||||
"teamCreateReq": {
|
||||
"access_token": userToken.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isSend = await friendSocketStore.send(createGroupData.value)
|
||||
if (isSend) {
|
||||
await sendCreateGroup();
|
||||
} else {
|
||||
console.log("发送新建群聊消息失败");
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
uni.showToast({
|
||||
title: `新建群聊失败${err}`,
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 发送创建群聊的socket消息
|
||||
const sendCreateGroup = () => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('超时未返回群聊创建结果'))
|
||||
}, 180000)
|
||||
|
||||
// 创建一个一次性监听器, // 监听command的值,如果是3就检查有没有创建群聊成功
|
||||
const unwatch =
|
||||
watch(() => friendSocketStore.command, async (newVal) => {
|
||||
console.log("创建一个一次性监听器");
|
||||
if (newVal && friendSocketStore.command === 3) {
|
||||
console.log("监听到command的值是3,检查群聊是否创建成功", friendSocketStore.command);
|
||||
try {
|
||||
await takeGroupList()
|
||||
const newGroup = GroupList.value.find(g => g.id === friendSocketStore
|
||||
.groupId)
|
||||
// 2. 判断是否新建成功
|
||||
if (newGroup) {
|
||||
console.log("✅ 新建群聊成功", newGroup);
|
||||
selectGroupMember.value = []
|
||||
currentTab.value = 0
|
||||
|
||||
} else {
|
||||
console.log("❌ 新建群聊失败 / 群组不存在");
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('保存AI回复失败:', error);
|
||||
|
||||
reject(error);
|
||||
} finally {
|
||||
friendSocketStore.command = null
|
||||
friendSocketStore.groupId = null
|
||||
unwatch();
|
||||
clearTimeout(timeout);
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 已选群成员列表
|
||||
const selectGroupMember = ref([]);
|
||||
const handlCreateEmember = (id) => {
|
||||
console.log("选择的好友id:",id);
|
||||
console.log("selectGroupMember.value=",JSON.stringify(selectGroupMember.value));
|
||||
const index = selectGroupMember.value.findIndex(item => item.id === id);
|
||||
if (index === -1) {
|
||||
const friendItem = FriendInfoList.value.find(item => item.receiver === id);
|
||||
if (friendItem) {
|
||||
selectGroupMember.value.push({
|
||||
id: friendItem.receiver,
|
||||
friendNickName: friendItem.friendNickName
|
||||
})
|
||||
}
|
||||
} else {
|
||||
selectGroupMember.value.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加好友相关
|
||||
const isSearchFriend = ref(false)
|
||||
const searchFriendName = ref('')
|
||||
const searchResultList = ref([]);
|
||||
const searchFriend = async() => {
|
||||
if (!searchFriendName.value.trim()) {
|
||||
searchResultList.value = []
|
||||
return
|
||||
}
|
||||
// 模糊查询:遍历 mockFriendDB,匹配 nickname、username 或 email
|
||||
const keyword = searchFriendName.value.toLowerCase().trim();
|
||||
searchResultList.value = await searchUsers(userToken.value,keyword)
|
||||
console.log("searchResultList.value:",searchResultList.value);
|
||||
// const results = mockFriendDB.value.filter(friend => {
|
||||
// const isMatch = friend.nickname.toLowerCase().includes(keyword) || friend.username.toLowerCase()
|
||||
// .includes(keyword) || friend.email.toLowerCase().includes(keyword);
|
||||
// return isMatch
|
||||
// })
|
||||
// searchResultList.value = results
|
||||
}
|
||||
const handleFriendSearchFocus = () => {
|
||||
isSearchFriend.value = !isSearchFriend.value
|
||||
}
|
||||
|
||||
const currentAddFriend = ref(null)
|
||||
const addNewFriend = (id) => {
|
||||
const friend = mockFriendDB.value.find(friend => friend.id === id)
|
||||
if (friend) {
|
||||
// 创建副本避免引用问题
|
||||
currentAddFriend.value = {
|
||||
...friend
|
||||
}
|
||||
} else {
|
||||
console.warn('未找到该好友')
|
||||
currentAddFriend.value = null
|
||||
}
|
||||
}
|
||||
const closeAddFriendCard = () => {
|
||||
currentAddFriend.value = null
|
||||
isAgreeBeFriend.value = false
|
||||
}
|
||||
|
||||
// 视频会议相关
|
||||
const isVoice = ref(false);
|
||||
const isCamera = ref(false)
|
||||
const togglesVoice = () => {
|
||||
isVoice.value = !isVoice.value
|
||||
}
|
||||
const togglesCamera = () => {
|
||||
isCamera.value = !isCamera.value
|
||||
}
|
||||
|
||||
// 好友申请相关
|
||||
const isAgreeBeFriend = ref(false)
|
||||
const refuseBeFriend = (friend) => {
|
||||
|
||||
}
|
||||
const agreeBeFriend = (friend) => {
|
||||
isAgreeBeFriend.value = true
|
||||
currentAddFriend.value = {
|
||||
...friend
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 模拟好友数据库
|
||||
const mockFriendDB = ref([{
|
||||
id: 1,
|
||||
nickname: '张三',
|
||||
username: 'zhangsan',
|
||||
email: 'zhangsan@example.com',
|
||||
avatar: '👤'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
nickname: '李四',
|
||||
username: 'lisi',
|
||||
email: 'lisi@example.com',
|
||||
avatar: '👥'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
nickname: '王小明',
|
||||
username: 'wangxm',
|
||||
email: 'wang@example.com',
|
||||
avatar: '👪'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
nickname: '赵磊',
|
||||
username: 'zhaolei',
|
||||
email: 'zhao@example.com',
|
||||
avatar: '🗣️'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
nickname: '张三2',
|
||||
username: 'zhangsan',
|
||||
email: 'zhangsan@example.com',
|
||||
avatar: '👤'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
nickname: '李四2',
|
||||
username: 'lisi',
|
||||
email: 'lisi@example.com',
|
||||
avatar: '👥'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
nickname: '王小明2',
|
||||
username: 'wangxm',
|
||||
email: 'wang@example.com',
|
||||
avatar: '👪'
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
nickname: '赵磊2',
|
||||
username: 'zhaolei',
|
||||
email: 'zhao@example.com',
|
||||
avatar: '🗣️'
|
||||
}
|
||||
|
||||
])
|
||||
|
||||
// 通讯录相关
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import url("ContactPages.css");
|
||||
</style>
|
||||
373
pages/Login/Login.vue
Normal file
373
pages/Login/Login.vue
Normal file
@@ -0,0 +1,373 @@
|
||||
<template>
|
||||
<view class="status-bar"></view>
|
||||
<view class="login-container page-container">
|
||||
<view class="login-card">
|
||||
<!-- <view class="conner conner-tl"></view>
|
||||
<view class="conner conner-tr"></view>
|
||||
<view class="conner conner-bl"></view>
|
||||
<view class="conner conner-br"></view> -->
|
||||
<view class="login-header">
|
||||
<view class="login-header-logo">
|
||||
<view class="icon-wrapper">
|
||||
<view class="iconfont icon-brain-2-fill danao-style"></view>
|
||||
</view>
|
||||
<text>YXD</text>
|
||||
</view>
|
||||
<view class="sub-title">File Handling Chat</view>
|
||||
|
||||
</view>
|
||||
<!-- 表单 -->
|
||||
<view class="form-wrapper">
|
||||
<view class="form-item">
|
||||
<view class="form-label">
|
||||
<!-- <uni-icons type="person" size="24" color="#aaaaff"></uni-icons> -->
|
||||
<text>用户名</text>
|
||||
</view>
|
||||
<input class="form-input" :class="{focused: isUsernameFocused, error: isUsernameError}"
|
||||
v-model="UsernameValue" @focus="usernameFocused" @blur="handleUsernameBlur"
|
||||
@input="validateUsername" placeholder="请输入用户名" />
|
||||
</view>
|
||||
<view class="form-item">
|
||||
<view class="form-label">
|
||||
<!-- <uni-icons type="locked" size="24" color="#aaaaff"></uni-icons> -->
|
||||
<text>密码</text>
|
||||
</view>
|
||||
<input type="password" class="form-input"
|
||||
:class="{focused: isPasswordFocused, error: isPasswordError}" v-model="PasswordValue"
|
||||
@focus="passwordFocused" @blur="handlePasswordBlur" placeholder="请输入密码" />
|
||||
</view>
|
||||
<text v-if="formDataHasNull" class="error-info">用户名或密码不能为空</text>
|
||||
<view class="form-option">
|
||||
<view class="checkbox-wrapper" @click="form.remember = !form.remember">
|
||||
<view class="checkbox" :class="{ checked: form.remember}">
|
||||
<text v-if="form.remember">✓</text>
|
||||
</view>
|
||||
<text class="checkbox-text">记住密码</text>
|
||||
|
||||
</view>
|
||||
<text class="forget-pwd">忘记密码?</text>
|
||||
</view>
|
||||
<button class="login-btn" :disabled="loading" :loading="loading"
|
||||
@click="handleLogin">{{loading ? 'Loading...' : '登录'}}</button>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
reactive,
|
||||
ref,
|
||||
computed,
|
||||
onMounted
|
||||
} from 'vue';
|
||||
import {
|
||||
login
|
||||
} from '../../utils/cloud-api';
|
||||
|
||||
// 表单用户名变量
|
||||
const UsernameValue = ref('');
|
||||
const isUsernameFocused = ref(false);
|
||||
const isUsernameError = ref(false)
|
||||
const usernameFocused = () => {
|
||||
isUsernameFocused.value = true;
|
||||
formDataHasNull.value = false;
|
||||
}
|
||||
const handleUsernameBlur = () => {
|
||||
isUsernameFocused.value = false;
|
||||
}
|
||||
// 用户名实时校验
|
||||
const validateUsername = () => {
|
||||
// if (UsernameValue.value && UsernameValue.value.length < 3) {
|
||||
// isUsernameError.value = true;
|
||||
// } else {
|
||||
// isUsernameError.value = false;
|
||||
// }
|
||||
};
|
||||
// 表单密码变量
|
||||
const PasswordValue = ref('');
|
||||
const isPasswordFocused = ref(false);
|
||||
const isPasswordError = ref(false);
|
||||
const passwordFocused = () => {
|
||||
isPasswordFocused.value = true;
|
||||
formDataHasNull.value = false;
|
||||
}
|
||||
const handlePasswordBlur = () => {
|
||||
isPasswordFocused.value = false;
|
||||
}
|
||||
|
||||
// 表单数据
|
||||
const form = reactive({
|
||||
username: computed(() => UsernameValue.value),
|
||||
password: computed(() => PasswordValue.value),
|
||||
remember: true
|
||||
})
|
||||
// 登录状态
|
||||
const loading = ref(false)
|
||||
|
||||
//用户名密码是否非空
|
||||
const formDataHasNull = ref(false)
|
||||
const handleLogin = async() => {
|
||||
checkFormData();
|
||||
if (formDataHasNull.value) return
|
||||
loading.value = true
|
||||
try {
|
||||
const token = await login(UsernameValue.value, PasswordValue.value)
|
||||
// 登陆成功,保存登录数据到手机
|
||||
saveLoginInfo();
|
||||
uni.reLaunch({
|
||||
url: '/pages/Chat/Chat'
|
||||
})
|
||||
} catch(err) {
|
||||
console.log('登录失败',err);
|
||||
uni.showToast({
|
||||
title:err || '失败',
|
||||
icon:'error'
|
||||
})
|
||||
}finally {
|
||||
loading.value = false
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// 检查用户名或密码是否为空
|
||||
const checkFormData = () => {
|
||||
if (!UsernameValue.value || !PasswordValue.value) {
|
||||
formDataHasNull.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 保存登录信息到手机
|
||||
const saveLoginInfo = () => {
|
||||
if (form.remember) {
|
||||
const loginInfo = {
|
||||
username: form.username,
|
||||
password: form.password,
|
||||
remember: true
|
||||
}
|
||||
uni.setStorageSync('login_info', loginInfo)
|
||||
uni.setStorageSync('chatType', 0)
|
||||
} else {
|
||||
uni.removeStorageSync('login_info')
|
||||
}
|
||||
}
|
||||
|
||||
// 从手机加载登录消息
|
||||
const loadLoginInfo = () => {
|
||||
try {
|
||||
const loginInfo = uni.getStorageSync('login_info')
|
||||
if (loginInfo) {
|
||||
UsernameValue.value = loginInfo.username || '';
|
||||
PasswordValue.value = loginInfo.password || '';
|
||||
form.remember = loginInfo.remember ?? true;
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('读取登录信息失败', e)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
loadLoginInfo();
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.login-container {
|
||||
padding: 140rpx 60rpx 100rpx 60rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.login-card {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.conner {
|
||||
position: absolute;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
|
||||
.conner-tl {
|
||||
top: 0;
|
||||
left: 0;
|
||||
border-top: 2px solid #aaaaff;
|
||||
border-left: 2px solid #aaaaff;
|
||||
}
|
||||
|
||||
.conner-tr {
|
||||
top: 0;
|
||||
right: 0;
|
||||
border-top: 2px solid #aaaaff;
|
||||
border-right: 2px solid #aaaaff;
|
||||
}
|
||||
|
||||
.conner-bl {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
border-bottom: 2px solid #aaaaff;
|
||||
border-left: 2px solid #aaaaff;
|
||||
}
|
||||
|
||||
.conner-br {
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
border-bottom: 2px solid #aaaaff;
|
||||
border-right: 2px solid #aaaaff;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.login-header {
|
||||
/* position: absolute;
|
||||
top: 80rpx; */
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.login-header-logo {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.login-header-logo text {
|
||||
margin-left: 8rpx;
|
||||
font-size: 60rpx;
|
||||
font-weight: bold;
|
||||
color: #aaaaff;
|
||||
text-shadow: 0 0 10rpx rgba(170, 170, 255, 0.4),
|
||||
0 0 20rpx rgba(170, 170, 255, 0.3);
|
||||
}
|
||||
|
||||
.icon-wrapper {
|
||||
width: 90rpx;
|
||||
height: 90rpx;
|
||||
border-radius: 50%;
|
||||
background-color: rgb(255, 255, 255);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
animation: glow 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes glow {
|
||||
|
||||
0%,
|
||||
100% {
|
||||
box-shadow: 0 0 20rpx rgba(170, 170, 255, 0.3);
|
||||
}
|
||||
|
||||
50% {
|
||||
box-shadow: 0 0 20rpx rgba(170, 170, 255, 0.4),
|
||||
0 0 30rpx rgba(170, 170, 255, 0.3),
|
||||
0 0 50rpx rgba(170, 170, 255, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.icon-wrapper .danao-style {
|
||||
font-size: 70rpx;
|
||||
color: #aaaaff;
|
||||
}
|
||||
|
||||
.sub-title {
|
||||
margin-top: 20rpx;
|
||||
font-size: 36rpx;
|
||||
font-weight: 500;
|
||||
color: #666;
|
||||
text-shadow: 0 0 10rpx #999;
|
||||
}
|
||||
|
||||
.form-wrapper {
|
||||
margin-top: 50rpx;
|
||||
width: 500rpx;
|
||||
padding: 80rpx 30rpx;
|
||||
background-color: rgba(170, 170, 255, 0.05);
|
||||
border-radius: 20rpx;
|
||||
/* border: 1rpx solid #aaaaff; */
|
||||
box-shadow: 0 0 10rpx rgba(170, 170, 255, 0.5);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.form-item {
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
|
||||
.form-item .form-input {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
padding: 0 10px;
|
||||
box-sizing: border-box;
|
||||
border-radius: 30rpx;
|
||||
border: 1rpx solid #e5e5e5;
|
||||
}
|
||||
|
||||
.form-item .focused {
|
||||
border-color: #aaaaff;
|
||||
box-shadow: 0 0 10rpx rgba(170, 170, 255, 0.5);
|
||||
}
|
||||
|
||||
.form-item .error {
|
||||
border-color: #ff0000;
|
||||
box-shadow: 0 0 10rpx rgba(255, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.error-info {
|
||||
font-size: 24rpx;
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
.form-option {
|
||||
margin-top: 10rpx;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.form-option .checkbox-wrapper {
|
||||
display: flex;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.form-option .checkbox-wrapper .checkbox {
|
||||
width: 30rpx;
|
||||
height: 30rpx;
|
||||
border: 1rpx solid #666;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.form-option .checkbox-wrapper .checked {
|
||||
background-color: #aaaaff;
|
||||
}
|
||||
|
||||
.form-option .checkbox-wrapper .checkbox text {
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
:deep(.login-btn) {
|
||||
margin-top: 30rpx;
|
||||
width: 500rpx;
|
||||
height: 100rpx;
|
||||
border-radius: 80rpx;
|
||||
background: linear-gradient(to right, #aaaaff, #2969ed);
|
||||
}
|
||||
</style>
|
||||
276
pages/UserProfileModal/UserProfileModal.vue
Normal file
276
pages/UserProfileModal/UserProfileModal.vue
Normal file
@@ -0,0 +1,276 @@
|
||||
<template>
|
||||
<view class="status-bar"></view>
|
||||
<view class="user-profile page-container">
|
||||
<view class="modal-header">
|
||||
<view class="modal-left" @click="closeUserCard">
|
||||
<view class="iconfont icon-fanhui custom-navbar-icon"></view>
|
||||
</view>
|
||||
<view class="modal-title">
|
||||
<view class="title-text">编辑资料</view>
|
||||
</view>
|
||||
<view class="modal-right" @click="saveUpdate">
|
||||
<view class="save-btn">保存</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="modal-body">
|
||||
<view class="avatar-upload-section" @click="triggerAvatarUpload">
|
||||
<view class="avatar-upload-container">
|
||||
<view v-if="editUserAvatar" class="avatar-preview">
|
||||
<image :src="editUserAvatar" mode="aspectFill"></image>
|
||||
</view>
|
||||
<view v-else class="avatar-placeholder">
|
||||
<uni-icons type="person-filled" size="100rpx" color="#ffffff"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="form-card">
|
||||
<view class="form-field">
|
||||
<view class="form-text">用户名</view>
|
||||
<input adjust-position="false" class="form-field-input form-field-username"
|
||||
:class="{'edit-input-focus':isNameFocus}" v-model="editUsername" @focus="isNameFocus=true"
|
||||
@blur="isNameFocus=false" />
|
||||
</view>
|
||||
<view class="solid-line"></view>
|
||||
<view class="form-field">
|
||||
<view class="form-text">邮箱</view>
|
||||
<input adjust-position="false" class="form-field-input form-field-emile"
|
||||
:class="{'edit-input-focus':isEmailFocus}" v-model="editEmail" @focus="isEmailFocus=true"
|
||||
@blur="isEmailFocus=false" />
|
||||
</view>
|
||||
</view>
|
||||
<view class="prompt-content">*保存后再返回哟</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
onMounted,
|
||||
ref
|
||||
} from 'vue';
|
||||
import { getUserInfo } from '@/utils/cloud-api.js'
|
||||
import {getToken} from '@/utils/user-info.js'
|
||||
|
||||
const closeUserCard = () => {
|
||||
console.log("点击了返回")
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
fail() {
|
||||
uni.reLaunch({
|
||||
url: '/pages/Chat/Chat'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const saveUpdate = () => {
|
||||
uni.navigateBack({
|
||||
url: '/pages/Chat/Chat'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const editUserAvatar = ref('')
|
||||
const editUsername = ref('')
|
||||
const editEmail = ref('')
|
||||
const isNameFocus = ref(false)
|
||||
const isEmailFocus = ref(false)
|
||||
|
||||
// 上传头像
|
||||
const triggerAvatarUpload = () => {
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => {
|
||||
const tempFilePath = res.tempFilePaths[0]
|
||||
handlrImageSelected(tempFilePath)
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log('选择图片失败', err);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 处理选择的图片
|
||||
const handlrImageSelected = (filePath) => {
|
||||
editUserAvatar.value = filePath
|
||||
}
|
||||
|
||||
onMounted(async()=>{
|
||||
const token = getToken()
|
||||
const UserInfo = await getUserInfo(token)
|
||||
|
||||
editUserAvatar.value = UserInfo.avatar || ''
|
||||
editUsername.value = UserInfo.username || ''
|
||||
editEmail.value = UserInfo.email || ''
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.user-profile {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
background-color: rgba(193, 193, 193, 0.1);
|
||||
}
|
||||
|
||||
|
||||
.modal-header {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
padding: 20rpx 20rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.modal-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
left: 50%;
|
||||
transform: translate(-50%);
|
||||
|
||||
.title-text {
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.modal-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
size: 40rpx;
|
||||
z-index: 2;
|
||||
|
||||
.save-btn {
|
||||
background-color: #3b82f6;
|
||||
color: #ffffff;
|
||||
padding: 8rpx 20rpx;
|
||||
box-sizing: border-box;
|
||||
border-radius: 10rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 50rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.avatar-upload-section {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
margin-bottom: 10rpx;
|
||||
box-sizing: border-box;
|
||||
|
||||
.avatar-upload-container {
|
||||
width: 200rpx;
|
||||
height: 200rpx;
|
||||
border-radius: 50%;
|
||||
padding: 5rpx;
|
||||
margin-top: 20rpx;
|
||||
box-sizing: border-box;
|
||||
background: #f3f4f6;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 5rpx solid #dddddd;
|
||||
box-sizing: border-box;
|
||||
|
||||
|
||||
.avatar-preview {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar-placeholder {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 10rpx;
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
background: #9ca3af;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-card {
|
||||
width: 100%;
|
||||
padding: 20rpx;
|
||||
background: #ffffff;
|
||||
box-sizing: border-box;
|
||||
border-radius: 20rpx;
|
||||
margin: 20rpx 0;
|
||||
}
|
||||
|
||||
.form-field {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
margin: 20rpx 0rpx;
|
||||
width: 100%;
|
||||
|
||||
.form-text {
|
||||
font-size: 15px;
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.form-field-input {
|
||||
width: 100%;
|
||||
height: 80rpx
|
||||
}
|
||||
|
||||
.edit-input-focus {
|
||||
background: rgba(170, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.form-field-username {
|
||||
|
||||
}
|
||||
|
||||
.form-field-emile {}
|
||||
|
||||
}
|
||||
|
||||
.prompt-content {
|
||||
width: 100%;
|
||||
font-size: 10px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
100
pages/WorkSpace/TemplateSpace/TemplateSpace.css
Normal file
100
pages/WorkSpace/TemplateSpace/TemplateSpace.css
Normal file
@@ -0,0 +1,100 @@
|
||||
/* 编辑模板弹窗 */
|
||||
.edit-template-wrapper {
|
||||
|
||||
}
|
||||
|
||||
.template-grid {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
gap: 20rpx;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.template-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
border-radius: 20rpx;
|
||||
border: 1rpx solid #ddd;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
|
||||
overflow: hidden;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.template-name {
|
||||
width: 100%;
|
||||
background: linear-gradient(to right, rgba(0, 122, 255, 0.8), rgba(170, 170, 255, 0.8));
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
font-size: 18px;
|
||||
padding: 10rpx 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.template-content {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
min-height: 200rpx;
|
||||
padding: 18rpx;
|
||||
margin: 6rpx 6rpx 0 6rpx;
|
||||
box-sizing: border-box;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 2rpx 5rpx 8rpx rgba(170, 170, 255, 0.3);
|
||||
overflow: hidden;
|
||||
|
||||
}
|
||||
|
||||
.template-content-text {
|
||||
width: 100%;
|
||||
color: #666;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 4;
|
||||
/* 限制显示行数 */
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.template-info-wrapper {
|
||||
padding: 0 16rpx;
|
||||
}
|
||||
|
||||
.template-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
color: #007aff;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.template-item .background-style {
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
border-radius: 10rpx;
|
||||
align-self: flex-start;
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
.template-option {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 10rpx;
|
||||
}
|
||||
|
||||
.template-option .iconfont {
|
||||
color: #007aff;
|
||||
}
|
||||
|
||||
.iconfont.icon-favorite-fill {
|
||||
color: #ffaa00;
|
||||
}
|
||||
482
pages/WorkSpace/TemplateSpace/TemplateSpace.vue
Normal file
482
pages/WorkSpace/TemplateSpace/TemplateSpace.vue
Normal file
@@ -0,0 +1,482 @@
|
||||
<template>
|
||||
<view class="status-bar"></view>
|
||||
<!-- 编辑模板弹窗 -->
|
||||
<view class="popup-overlay">
|
||||
<view class="popup-card">
|
||||
<view class="close-popup-card"><view class="iconfont icon-quxiao"></view></view>
|
||||
<view class="popup-title">编辑模板</view>
|
||||
<view class="edit-template-wrapper">
|
||||
<view class="edit-template-name">
|
||||
<view>模板名称</view>
|
||||
<input placeholder-style="请输入模板名称" v-model="templateName"/>
|
||||
</view>
|
||||
<view class="edit-template-name">
|
||||
<view>模板内容</view>
|
||||
<view class="markdown-editor-pane">
|
||||
<view class="markdown-pane-header">
|
||||
编辑
|
||||
</view>
|
||||
<textarea>
|
||||
|
||||
</textarea>
|
||||
</view>
|
||||
<view class="markdown-preview-pane">
|
||||
<view class="markdown-pane-header">
|
||||
预览
|
||||
</view>
|
||||
<view></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="workspace-container page-container">
|
||||
|
||||
<view class="workspace-header">
|
||||
<view class="custom-navbar">
|
||||
<view class="navbar-left" @click="handleBackOrCheck ">
|
||||
<view class="iconfont icon-fanhui custom-navbar-icon"></view>
|
||||
<view class="navbar-before-title workspace-text">{{isSelectFolder?'全选':navbarBeforeTitle}}</view>
|
||||
</view>
|
||||
|
||||
<view class="navbar-title workspace-text">{{navbarTitle}}</view>
|
||||
<view class="navbar-right" v-if="checkTemplate">
|
||||
<view v-if="isSelectFolder" class="navbar-right-text workspace-text" @click="handleSelectFolder()">
|
||||
完成</view>
|
||||
<view v-else class="iconfont icon-gengduo" :class="isMenuOpen ?'menu-open':'custom-navbar-icon'"
|
||||
@click="handleMenu"></view>
|
||||
</view>
|
||||
<view v-if="isMenuOpen" class="menu-card">
|
||||
<view class="menu-card-item" @click="handleSelectFolder();handleMenu()">选择</view>
|
||||
<view class="solid-line"></view>
|
||||
<view class="menu-card-item">新建模板</view>
|
||||
<view class="solid-line"></view>
|
||||
<view class="menu-card-item">上传模板</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="search-file-warpper">
|
||||
<view class="search-file">
|
||||
<view class="iconfont icon-sousuo"></view>
|
||||
<input class="search-file-input" placeholder="搜索" @focus="handleSearchFocus"
|
||||
v-model="searchKeyword" />
|
||||
</view>
|
||||
<view v-if="isSearchFocus" class="cancel-search" @click="handleSearchFocus">取消</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<view class="folder-grid" v-if="!checkTemplate">
|
||||
<view class="folder-item" @click="checkShowTemplate('all')">
|
||||
<view class="iconfont icon-a-wenjianjiawenjian folder-item-style"></view>
|
||||
<view class="folder-name">全部模板</view>
|
||||
</view>
|
||||
<view class="folder-item" @click="checkShowTemplate('like')">
|
||||
<view class="iconfont icon-a-wenjianjiawenjian folder-item-style"></view>
|
||||
<view class="folder-name">收藏模板</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="workspace-content" v-if="checkTemplate">
|
||||
|
||||
<view class="folder-null" v-if="currentTemplateList.length===0">
|
||||
<view class="iconfont icon-wenjianjia"></view>
|
||||
暂无模板
|
||||
</view>
|
||||
<view class="template-grid" v-if="currentTemplateList.length>0">
|
||||
<view class="template-card" v-for="(item,index) in currentTemplateList" :key="index">
|
||||
|
||||
<view class="template-name">{{item.name}}</view>
|
||||
<view class="template-content">
|
||||
<view class="template-content-text">
|
||||
{{item.content}}
|
||||
</view>
|
||||
</view>
|
||||
<view class="template-info-wrapper">
|
||||
|
||||
<view class="template-item">
|
||||
<view class="template-time background-style">{{formatTime(item.modified_time)}}</view>
|
||||
<view class="template-cache background-style">{{formatSize(item.size)}}</view>
|
||||
</view>
|
||||
<view class="template-option">
|
||||
<view class="iconfont" :class="item.is_favorite ? 'icon-favorite-fill':'icon-favorite'"
|
||||
@click="handleLikeTemplate(index)"></view>
|
||||
<view class="iconfont icon-edit-fill" @click="editTemplate"></view>
|
||||
<view class="iconfont icon-choose-fill"></view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="isSelectFolder" class="workspace-footer" :class="{'folder-actions':selectFileList.length>0}">
|
||||
<view class="iconfont icon-shangchuan"></view>
|
||||
<view class="iconfont icon-fuzhi"></view>
|
||||
<view class="iconfont icon-wenjian"></view>
|
||||
<view class="iconfont icon-del"></view>
|
||||
<view class="iconfont icon-gengduo"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
onMounted,
|
||||
ref
|
||||
} from 'vue';
|
||||
|
||||
// 格式化时间函数
|
||||
const formatTime = (timestamp) => {
|
||||
if (!timestamp) return ''
|
||||
|
||||
// 处理包含毫秒的时间戳
|
||||
const seconds = Math.floor(timestamp)
|
||||
const date = new Date(seconds * 1000)
|
||||
|
||||
const year = date.getFullYear()
|
||||
const month = (date.getMonth() + 1).toString().padStart(2, '0')
|
||||
const day = date.getDate().toString().padStart(2, '0')
|
||||
const hours = date.getHours().toString().padStart(2, '0')
|
||||
const minutes = date.getMinutes().toString().padStart(2, '0')
|
||||
const seconds_part = date.getSeconds().toString().padStart(2, '0')
|
||||
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds_part}`
|
||||
}
|
||||
|
||||
// 转换字节大小
|
||||
const formatSize = (bytes) => {
|
||||
if (!bytes || bytes === 0) return '0 B'
|
||||
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
let size = bytes
|
||||
let unitIndex = 0
|
||||
|
||||
while (size >= 1024 && unitIndex < units.length - 1) {
|
||||
size /= 1024
|
||||
unitIndex++
|
||||
}
|
||||
|
||||
// 保留一位或两位小数
|
||||
return `${size.toFixed(1)} ${units[unitIndex]}`
|
||||
}
|
||||
|
||||
// 核心:点赞/取消点赞
|
||||
const handleLikeTemplate = (index) => {
|
||||
let targetItem
|
||||
|
||||
// 1. 根据当前展示类型,找到真实被点击的项
|
||||
if (checkTemplate.value === 'all') {
|
||||
// 全部列表:直接用 index
|
||||
targetItem = allTemplate.value[index]
|
||||
} else if (checkTemplate.value === 'like') {
|
||||
// 收藏列表:先过滤出收藏项,再用 index 找到
|
||||
const favoriteList = allTemplate.value.filter(item => item.is_favorite)
|
||||
targetItem = favoriteList[index]
|
||||
}
|
||||
|
||||
// 2. 切换收藏状态
|
||||
if (targetItem) {
|
||||
targetItem.is_favorite = !targetItem.is_favorite
|
||||
}
|
||||
|
||||
// 3. 关键:切换后重新刷新当前列表(否则页面不更新)
|
||||
if (checkTemplate.value === 'all') {
|
||||
currentTemplateList.value = [...allTemplate.value]
|
||||
} else {
|
||||
currentTemplateList.value = allTemplate.value.filter(item => item.is_favorite)
|
||||
}
|
||||
}
|
||||
// 编辑模板
|
||||
const templateName = ref('aaa')
|
||||
const editTemplate = () => {
|
||||
console.log('点击了编辑模板');
|
||||
}
|
||||
|
||||
// 选择的模板区(全部or收藏)
|
||||
const checkTemplate = ref('')
|
||||
const currentTemplateList = ref([])
|
||||
const checkShowTemplate = (type) => {
|
||||
checkTemplate.value = type;
|
||||
if (type === 'all') {
|
||||
navbarTitle.value = '全部模板'
|
||||
currentTemplateList.value = allTemplate.value
|
||||
}
|
||||
if (type === 'like') {
|
||||
navbarTitle.value = '收藏模板'
|
||||
currentTemplateList.value = allTemplate.value.filter(item => item.is_favorite === true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const navbarBeforeTitle = ref('');
|
||||
const navbarTitle = ref('模板区');
|
||||
|
||||
// 选择文件模式
|
||||
const isSelectFolder = ref(false);
|
||||
const selectFileList = ref([]);
|
||||
const handleSelectFolder = () => {
|
||||
if (isSelectFolder.value) {
|
||||
selectFileList.value = []
|
||||
}
|
||||
isSelectFolder.value = !isSelectFolder.value;
|
||||
}
|
||||
const selectFolder = (id) => {
|
||||
const index = selectFileList.value.indexOf(id);
|
||||
if (index !== -1) {
|
||||
selectFileList.value.splice(index, 1);
|
||||
} else {
|
||||
selectFileList.value.push(id);
|
||||
}
|
||||
}
|
||||
const handleLongPress = (folder) => {
|
||||
if (isSelectFolder.value) return;
|
||||
uni.showActionSheet({
|
||||
itemList: ['重命名', '删除', '下载', '压缩'],
|
||||
success: (res) => {
|
||||
switch (res.tapIndex) {
|
||||
case 0:
|
||||
handleRename(folder);
|
||||
break;
|
||||
case 1:
|
||||
handleDelete(folder);
|
||||
break;
|
||||
case 2:
|
||||
handleDownload(folder);
|
||||
break;
|
||||
case 3:
|
||||
handleDownload(folder);
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 重命名
|
||||
const handleRename = (folder) => {
|
||||
uni.showModal({
|
||||
title: '重命名',
|
||||
content: '请输入新名称',
|
||||
editable: true,
|
||||
placeholderText: folder.name,
|
||||
success: (res) => {
|
||||
if (res.confirm && res.content) {
|
||||
// 更新文件夹名称
|
||||
updateFolderName(folder.id, res.content);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 移动
|
||||
const handleDownload = (folder) => {
|
||||
// 可以选择跳转到移动页面或显示选择器
|
||||
uni.showToast({
|
||||
title: '移动功能开发中',
|
||||
icon: 'none'
|
||||
});
|
||||
};
|
||||
|
||||
// 删除
|
||||
const handleDelete = (folder) => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: `确定要删除文件夹"${folder.name}"吗?`,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// deleteFolder(folder.id);
|
||||
console.log('确认删除文件夹');
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// const handleFolderClick = (id) => {
|
||||
// console.log(id);
|
||||
// }
|
||||
|
||||
// 菜单
|
||||
const isMenuOpen = ref(false)
|
||||
const handleMenu = () => {
|
||||
isMenuOpen.value = !isMenuOpen.value;
|
||||
}
|
||||
|
||||
// 搜索框
|
||||
const searchKeyword = ref('')
|
||||
const isSearchFocus = ref(false)
|
||||
const handleSearchFocus = () => {
|
||||
isSearchFocus.value = !isSearchFocus.value
|
||||
}
|
||||
|
||||
const allTemplate = ref([{
|
||||
"name": "SKILL.md",
|
||||
"type": "file",
|
||||
"path": "SKILL.md",
|
||||
"children": [],
|
||||
"file_count": 0,
|
||||
"directory_count": 0,
|
||||
"size": 4768,
|
||||
"modified_time": 1776827361.9123552,
|
||||
"extension": ".md",
|
||||
"is_favorite": false,
|
||||
"usage_count": 0,
|
||||
"sort": 0,
|
||||
"content": 'khgvshbbthvgbihepiubjhrviifdhiuhrviuehrviuawhiuvrhy'
|
||||
},
|
||||
{
|
||||
"name": "222.md",
|
||||
"type": "file",
|
||||
"path": "222.md",
|
||||
"children": [],
|
||||
"file_count": 0,
|
||||
"directory_count": 0,
|
||||
"size": 5558,
|
||||
"modified_time": 1776827666.9123552,
|
||||
"extension": ".md",
|
||||
"is_favorite": false,
|
||||
"usage_count": 0,
|
||||
"sort": 0,
|
||||
"content": 'khgvshbbthvgbihepiubjhiuvrhy'
|
||||
},
|
||||
{
|
||||
"name": "222.md",
|
||||
"type": "file",
|
||||
"path": "222.md",
|
||||
"children": [],
|
||||
"file_count": 0,
|
||||
"directory_count": 0,
|
||||
"size": 5558,
|
||||
"modified_time": 1776827666.9123552,
|
||||
"extension": ".md",
|
||||
"is_favorite": false,
|
||||
"usage_count": 0,
|
||||
"sort": 0,
|
||||
"content": '该回家看了的法国红酒看来法帝国海军快来尝尝v吧给v复仇计划曝光vi计划日方提供与i哦的风格和健康'
|
||||
},
|
||||
{
|
||||
"name": "222.md",
|
||||
"type": "file",
|
||||
"path": "222.md",
|
||||
"children": [],
|
||||
"file_count": 0,
|
||||
"directory_count": 0,
|
||||
"size": 5558,
|
||||
"modified_time": 1776827666.9123552,
|
||||
"extension": ".md",
|
||||
"is_favorite": false,
|
||||
"usage_count": 0,
|
||||
"sort": 0,
|
||||
"content": '的法国红酒看来法帝国海军快来尝尝v吧给'
|
||||
},
|
||||
{
|
||||
"name": "222.md",
|
||||
"type": "file",
|
||||
"path": "222.md",
|
||||
"children": [],
|
||||
"file_count": 0,
|
||||
"directory_count": 0,
|
||||
"size": 5558,
|
||||
"modified_time": 1776827666.9123552,
|
||||
"extension": ".md",
|
||||
"is_favorite": false,
|
||||
"usage_count": 0,
|
||||
"sort": 0,
|
||||
"content": ''
|
||||
},
|
||||
{
|
||||
"name": "222.md",
|
||||
"type": "file",
|
||||
"path": "222.md",
|
||||
"children": [],
|
||||
"file_count": 0,
|
||||
"directory_count": 0,
|
||||
"size": 5558,
|
||||
"modified_time": 1776827666.9123552,
|
||||
"extension": ".md",
|
||||
"is_favorite": false,
|
||||
"usage_count": 0,
|
||||
"sort": 0,
|
||||
"content": 'khgvshbbthvgbihepiubjhrviifdhiuhrviuehrviuawhiuvrhy'
|
||||
},
|
||||
{
|
||||
"name": "222.md",
|
||||
"type": "file",
|
||||
"path": "222.md",
|
||||
"children": [],
|
||||
"file_count": 0,
|
||||
"directory_count": 0,
|
||||
"size": 5558,
|
||||
"modified_time": 1776827666.9123552,
|
||||
"extension": ".md",
|
||||
"is_favorite": false,
|
||||
"usage_count": 0,
|
||||
"sort": 0,
|
||||
"content": 'khgvshbbthvgbihepiubjhrviifdhiuhrviuehrviuawhiuvrhy'
|
||||
},
|
||||
{
|
||||
"name": "222.md",
|
||||
"type": "file",
|
||||
"path": "222.md",
|
||||
"children": [],
|
||||
"file_count": 0,
|
||||
"directory_count": 0,
|
||||
"size": 5558,
|
||||
"modified_time": 1776827666.9123552,
|
||||
"extension": ".md",
|
||||
"is_favorite": false,
|
||||
"usage_count": 0,
|
||||
"sort": 0,
|
||||
"content": 'khgvshbbthvgbihepiubjhrviifdhiuhrviuehrviuawhiuvrhy'
|
||||
},
|
||||
{
|
||||
"name": "222.md",
|
||||
"type": "file",
|
||||
"path": "222.md",
|
||||
"children": [],
|
||||
"file_count": 0,
|
||||
"directory_count": 0,
|
||||
"size": 5558,
|
||||
"modified_time": 1776827666.9123552,
|
||||
"extension": ".md",
|
||||
"is_favorite": false,
|
||||
"usage_count": 0,
|
||||
"sort": 0,
|
||||
"content": 'khgvshbbthvgbihepiubjhrviifdhiuhrviuehrviuawhiuvrhy'
|
||||
}
|
||||
])
|
||||
|
||||
// 全选/取消全选
|
||||
const handleSelectAll = () => {
|
||||
if (selectFileList.value.length === currentTemplateList.value.length) {
|
||||
selectFileList.value = [];
|
||||
} else {
|
||||
selectFileList.value = currentTemplateList.value.map(f => f.id);
|
||||
}
|
||||
};
|
||||
|
||||
// 返回上一级
|
||||
const handleBackOrCheck = () => {
|
||||
if (isSelectFolder.value) {
|
||||
// 选择模式下点击全选/取消全选
|
||||
handleSelectAll();
|
||||
return;
|
||||
}
|
||||
if (!isSelectFolder.value && currentTemplateList.value) {
|
||||
currentTemplateList.value = []
|
||||
checkTemplate.value = ''
|
||||
navbarTitle.value = '模板区'
|
||||
}
|
||||
if (!isSelectFolder.value && !currentTemplateList.value) {
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
fail() {
|
||||
console.log("返回失败,进入兜底跳转")
|
||||
uni.reLaunch({
|
||||
url: '/pages/Chat/Chat'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import url('../WorkSpace.css');
|
||||
@import url('TemplateSpace.css');
|
||||
</style>
|
||||
219
pages/WorkSpace/WorkSpace.css
Normal file
219
pages/WorkSpace/WorkSpace.css
Normal file
@@ -0,0 +1,219 @@
|
||||
.workspace-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: rgba(140, 140, 140, 0.1);
|
||||
}
|
||||
|
||||
.workspace-text {
|
||||
color: #0073ff;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.workspace-header {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 16rpx 0;
|
||||
box-sizing: border-box;
|
||||
border-bottom: 1rpx solid #ddd;
|
||||
}
|
||||
|
||||
.custom-navbar {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 20rpx 10rpx;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.navbar-left {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.navbar-before-title {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.navbar-title {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.navbar-right {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.navbar-right .menu-open {
|
||||
color: #3ab0ff !important;
|
||||
font-size: 40rpx !important;
|
||||
}
|
||||
|
||||
.custom-navbar-icon {
|
||||
color: #0073ff !important;
|
||||
font-size: 40rpx !important;
|
||||
}
|
||||
|
||||
.search-file-warpper {
|
||||
display: flex;
|
||||
width: 90%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-file {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
background: rgba(255, 255, 255, 1);
|
||||
padding: 16rpx;
|
||||
box-sizing: border-box;
|
||||
border-radius: 30rpx;
|
||||
}
|
||||
|
||||
.icon-sousuo {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.search-file-input {
|
||||
margin-left: 20rpx;
|
||||
}
|
||||
|
||||
.cancel-search {
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
padding: 0 0 0 20rpx;
|
||||
box-sizing: border-box;
|
||||
color: #0073ff;
|
||||
}
|
||||
|
||||
.workspace-content {
|
||||
width: 100%;
|
||||
display: block;
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 菜单卡片 */
|
||||
.menu-card {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 20rpx;
|
||||
width: 300rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background: #fff;
|
||||
border-radius: 30rpx;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.menu-card-item {
|
||||
width: 100%;
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.folder-null {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 30%;
|
||||
transform: translateX(-50%);
|
||||
font-size: 32rpx;
|
||||
}
|
||||
.folder-null .iconfont {
|
||||
font-size: 180rpx;
|
||||
}
|
||||
|
||||
.folder-grid {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3,1fr);
|
||||
padding: 20rpx;
|
||||
box-sizing: border-box;
|
||||
gap: 20rpx;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.folder-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.folder-item.select-folder {
|
||||
border-radius: 30rpx;
|
||||
background: rgba(9, 9, 9, 0.1);
|
||||
}
|
||||
|
||||
.folder-item-style {
|
||||
font-size: 160rpx !important;
|
||||
font-weight: 10rpx !important;
|
||||
color: #3ab0ff !important;
|
||||
}
|
||||
|
||||
.folder-checkbox {
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
border: 6rpx solid #fff;
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 2rpx 3rpx #333;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 30%;
|
||||
transform: translate(-50%);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.folder-checkbox.select-folder {
|
||||
background-color: #0073ff;
|
||||
}
|
||||
|
||||
.workspace-footer {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
justify-content: space-between;
|
||||
padding: 30rpx;
|
||||
box-sizing: border-box;
|
||||
color: #999999;
|
||||
background: #eeeeee;
|
||||
}
|
||||
|
||||
.workspace-footer.folder-actions {
|
||||
color: #0073ff;
|
||||
}
|
||||
|
||||
.workspace-footer .iconfont {
|
||||
font-size: 40rpx !important;
|
||||
}
|
||||
384
pages/WorkSpace/WorkSpace.vue
Normal file
384
pages/WorkSpace/WorkSpace.vue
Normal file
@@ -0,0 +1,384 @@
|
||||
<template>
|
||||
<view class="status-bar"></view>
|
||||
<view class="workspace-container page-container">
|
||||
|
||||
<view class="workspace-header">
|
||||
<view class="custom-navbar">
|
||||
<view class="navbar-left" @click="handleBackOrCheck ">
|
||||
<view class="iconfont icon-fanhui custom-navbar-icon"></view>
|
||||
<view class="navbar-before-title workspace-text">{{isSelectFolder?'全选':navbarBeforeTitle}}</view>
|
||||
</view>
|
||||
|
||||
<view class="navbar-title workspace-text">{{navbarTitle}}</view>
|
||||
<view class="navbar-right">
|
||||
<view v-if="isSelectFolder" class="navbar-right-text workspace-text" @click="handleSelectFolder()">
|
||||
完成</view>
|
||||
<view v-else class="iconfont icon-gengduo" :class="isMenuOpen ?'menu-open':'custom-navbar-icon'"
|
||||
@click="handleMenu"></view>
|
||||
</view>
|
||||
<view v-if="isMenuOpen" class="menu-card">
|
||||
<view class="menu-card-item" @click="handleSelectFolder();handleMenu()">选择</view>
|
||||
<view class="solid-line"></view>
|
||||
<view class="menu-card-item">新建文件夹</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="search-file-warpper">
|
||||
<view class="search-file">
|
||||
<view class="iconfont icon-sousuo"></view>
|
||||
<input class="search-file-input" placeholder="搜索" @focus="handleSearchFocus"
|
||||
v-model="searchKeyword" />
|
||||
</view>
|
||||
<view v-if="isSearchFocus" class="cancel-search" @click="handleSearchFocus">取消</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<view class="workspace-content">
|
||||
<view class="folder-null" v-if="currentFolderList.length===0">
|
||||
<view class="iconfont icon-wenjianjia"></view>
|
||||
文件夹为空
|
||||
</view>
|
||||
<view class="folder-grid" v-if="currentFolderList.length>0">
|
||||
<view class="folder-item" :class="{'select-folder':selectFileList.includes(folder.id)}"
|
||||
v-for="folder in currentFolderList" :key="folder.id"
|
||||
@click="isSelectFolder?selectFolder(folder.id):handleFolderClick(folder)"
|
||||
@longpress="handleLongPress(folder)">
|
||||
<view class="iconfont icon-a-wenjianjiawenjian folder-item-style"></view>
|
||||
<view class="folder-name">{{folder.name}}</view>
|
||||
<view v-if="isSelectFolder" class="folder-checkbox"
|
||||
:class="{'select-folder':selectFileList.includes(folder.id)}">
|
||||
<uni-icons v-if="selectFileList.includes(folder.id)" type="checkmarkempty"
|
||||
color="#fff"></uni-icons>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view v-if="isSelectFolder" class="workspace-footer" :class="{'folder-actions':selectFileList.length>0}">
|
||||
<view class="iconfont icon-shangchuan"></view>
|
||||
<view class="iconfont icon-fuzhi"></view>
|
||||
<view class="iconfont icon-wenjian"></view>
|
||||
<view class="iconfont icon-del"></view>
|
||||
<view class="iconfont icon-gengduo"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
onMounted,
|
||||
ref
|
||||
} from 'vue';
|
||||
import {getWorkspaceId} from '@/utils/user-info.js'
|
||||
import {getWorkspaceList} from '@/utils/cloud-api.js'
|
||||
|
||||
const workspaceId = ref('')
|
||||
const navbarBeforeTitle = ref('');
|
||||
const navbarTitle = ref('工作区');
|
||||
|
||||
// 选择文件模式
|
||||
const isSelectFolder = ref(false);
|
||||
const selectFileList = ref([]);
|
||||
const handleSelectFolder = () => {
|
||||
if (isSelectFolder.value) {
|
||||
selectFileList.value = []
|
||||
}
|
||||
isSelectFolder.value = !isSelectFolder.value;
|
||||
}
|
||||
const selectFolder = (id) => {
|
||||
const index = selectFileList.value.indexOf(id);
|
||||
if (index !== -1) {
|
||||
selectFileList.value.splice(index, 1);
|
||||
} else {
|
||||
selectFileList.value.push(id);
|
||||
}
|
||||
}
|
||||
const handleLongPress = (folder) => {
|
||||
if (isSelectFolder.value) return;
|
||||
uni.showActionSheet({
|
||||
itemList:['重命名','删除','下载','压缩'],
|
||||
success: (res) => {
|
||||
switch(res.tapIndex) {
|
||||
case 0:
|
||||
handleRename(folder);
|
||||
break;
|
||||
case 1:
|
||||
handleDelete(folder);
|
||||
break;
|
||||
case 2:
|
||||
handleDownload(folder);
|
||||
break;
|
||||
case 3:
|
||||
handleDownload(folder);
|
||||
break;
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 重命名
|
||||
const handleRename = (folder) => {
|
||||
uni.showModal({
|
||||
title: '重命名',
|
||||
content: '请输入新名称',
|
||||
editable: true,
|
||||
placeholderText: folder.name,
|
||||
success: (res) => {
|
||||
if (res.confirm && res.content) {
|
||||
// 更新文件夹名称
|
||||
updateFolderName(folder.id, res.content);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 移动
|
||||
const handleDownload = (folder) => {
|
||||
// 可以选择跳转到移动页面或显示选择器
|
||||
uni.showToast({
|
||||
title: '移动功能开发中',
|
||||
icon: 'none'
|
||||
});
|
||||
};
|
||||
|
||||
// 删除
|
||||
const handleDelete = (folder) => {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: `确定要删除文件夹"${folder.name}"吗?`,
|
||||
success: (res) => {
|
||||
if (res.confirm) {
|
||||
// deleteFolder(folder.id);
|
||||
console.log('确认删除文件夹');
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// const handleFolderClick = (id) => {
|
||||
// console.log(id);
|
||||
// }
|
||||
|
||||
// 菜单
|
||||
const isMenuOpen = ref(false)
|
||||
const handleMenu = () => {
|
||||
isMenuOpen.value = !isMenuOpen.value;
|
||||
}
|
||||
|
||||
// 搜索框
|
||||
const searchKeyword = ref('')
|
||||
const isSearchFocus = ref(false)
|
||||
const handleSearchFocus = () => {
|
||||
isSearchFocus.value = !isSearchFocus.value
|
||||
}
|
||||
|
||||
const allFoldersData = ref({
|
||||
// 根目录下的文件夹
|
||||
'root': [{
|
||||
id: 1,
|
||||
name: '文档资料',
|
||||
parentId: 'root',
|
||||
children: [{
|
||||
id: 11,
|
||||
name: '工作文档',
|
||||
parentId: 1,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
name: '学习笔记',
|
||||
parentId: 1,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: 13,
|
||||
name: '合同模板',
|
||||
parentId: 1,
|
||||
children: []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '图片素材',
|
||||
parentId: 'root',
|
||||
children: [{
|
||||
id: 21,
|
||||
name: '风景图片',
|
||||
parentId: 2,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: 22,
|
||||
name: '人物照片',
|
||||
parentId: 2,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: 23,
|
||||
name: 'UI图标',
|
||||
parentId: 2,
|
||||
children: []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '视频文件',
|
||||
parentId: 'root',
|
||||
children: [{
|
||||
id: 31,
|
||||
name: '教程视频',
|
||||
parentId: 3,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: 32,
|
||||
name: '会议录像',
|
||||
parentId: 3,
|
||||
children: []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: '项目代码',
|
||||
parentId: 'root',
|
||||
children: [{
|
||||
id: 41,
|
||||
name: '前端项目',
|
||||
parentId: 4,
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: 42,
|
||||
name: '后端服务',
|
||||
parentId: 4,
|
||||
children: []
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: '安装包',
|
||||
parentId: 'root',
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: '备份文件',
|
||||
parentId: 'root',
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: '临时文件',
|
||||
parentId: 'root',
|
||||
children: []
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: '个人收藏',
|
||||
parentId: 'root',
|
||||
children: []
|
||||
}
|
||||
]
|
||||
});
|
||||
// 文件夹路径栈
|
||||
const folderStack = ref([]);
|
||||
// 当前显示的文件夹列表
|
||||
const currentFolderList = ref([]);
|
||||
|
||||
const getCurrentFolderList = () => {
|
||||
const currentPath = folderStack.value.length > 0 ? folderStack.value[folderStack.value.length - 1].id : 'root';
|
||||
if (currentPath === "root") {
|
||||
return allFoldersData.value['root'] || [];
|
||||
}
|
||||
const findFolderById = (folders, id) => {
|
||||
for (const folder of folders) {
|
||||
if (folder.id === id) return folder;
|
||||
if (folder.children && folder.children.length > 0) {
|
||||
const found = findFolderById(folder.children, id);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
};
|
||||
const currentFolder = findFolderById(allFoldersData.value['root'], currentPath);
|
||||
return currentFolder ? currentFolder.children : [];
|
||||
}
|
||||
|
||||
// 初始化当前文件夹列表
|
||||
const initCurrentFolderList = () => {
|
||||
workspaceId.value = getWorkspaceId();
|
||||
console.log("getWorkspaceId:",workspaceId.value);
|
||||
currentFolderList.value = getCurrentFolderList();
|
||||
};
|
||||
|
||||
// 处理文件夹点击(非选择模式下进入文件夹)
|
||||
const handleFolderClick = (folder) => {
|
||||
folderStack.value.push({
|
||||
id: folder.id,
|
||||
name: folder.name
|
||||
});
|
||||
navbarTitle.value = folder.name;
|
||||
navbarBeforeTitle.value = folderStack.value.length > 1 ? folderStack.value[folderStack.value.length - 2].name :
|
||||
'工作区';
|
||||
initCurrentFolderList();
|
||||
searchKeyword.value = '';
|
||||
isSearchFocus.value = false;
|
||||
};
|
||||
|
||||
// 全选/取消全选
|
||||
const handleSelectAll = () => {
|
||||
if (selectFileList.value.length === currentFolderList.value.length) {
|
||||
selectFileList.value = [];
|
||||
} else {
|
||||
selectFileList.value = currentFolderList.value.map(f => f.id);
|
||||
}
|
||||
};
|
||||
|
||||
// 返回上一级
|
||||
const handleBackOrCheck = () => {
|
||||
if (isSelectFolder.value) {
|
||||
// 选择模式下点击全选/取消全选
|
||||
handleSelectAll();
|
||||
return;
|
||||
}
|
||||
|
||||
if (folderStack.value.length > 0) {
|
||||
folderStack.value.pop();
|
||||
if (folderStack.value.length === 0) {
|
||||
navbarTitle.value = '工作区';
|
||||
navbarBeforeTitle.value = null;
|
||||
} else {
|
||||
navbarTitle.value = folderStack.value[folderStack.value.length - 1].name;
|
||||
navbarBeforeTitle.value = folderStack.value.length > 1 ?
|
||||
folderStack.value[folderStack.value.length - 2].name :
|
||||
'工作区';
|
||||
}
|
||||
initCurrentFolderList();
|
||||
// 返回后清空搜索
|
||||
searchKeyword.value = '';
|
||||
isSearchFocus.value = false;
|
||||
} else {
|
||||
// 已在根目录,返回到聊天界面
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
fail() {
|
||||
console.log("返回失败,进入兜底跳转")
|
||||
uni.reLaunch({
|
||||
url: '/pages/Chat/Chat'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
initCurrentFolderList();
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@import url("WorkSpace.css");
|
||||
</style>
|
||||
160
pages/text/text.vue
Normal file
160
pages/text/text.vue
Normal file
@@ -0,0 +1,160 @@
|
||||
<template>
|
||||
<view>
|
||||
<view v-html="sanitizeContent(formHtml)"></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
|
||||
const formHtml = ref('')
|
||||
const sanitizeContent = (str) => {
|
||||
return str.replace(/<\/?script>/gi, (match) => {
|
||||
// 将匹配到的标签转换为十六进制 Unicode
|
||||
let result = '';
|
||||
for (let i = 0; i < match.length; i++) {
|
||||
result += '\\u' + match.charCodeAt(i).toString(16).padStart(4, '0');
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// 直接设置 HTML
|
||||
formHtml.value = `
|
||||
明白了!您希望在我的回复中直接嵌入可编辑的表单卡片。让我试试:
|
||||
|
||||
---
|
||||
|
||||
<div style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 20px; padding: 28px; color: white; font-family: 'Microsoft YaHei', sans-serif; box-shadow: 0 10px 40px rgba(0,0,0,0.25); margin: 20px 0;">
|
||||
<h2 style="margin: 0 0 20px 0; font-size: 20px; display: flex; align-items: center; gap: 12px;">
|
||||
<span style="font-size: 26px;">✏️</span> 直接在下方编辑任务
|
||||
</h2>
|
||||
|
||||
<form id="inlineTaskForm" style="background: rgba(255,255,255,0.1); border-radius: 16px; padding: 24px;">
|
||||
<div style="margin-bottom: 18px;">
|
||||
<label style="display: block; font-size: 12px; opacity: 0.8; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 1px;">任务名称</label>
|
||||
<input type="text" id="inlineTitle" placeholder="输入任务名称..." style="width: 100%; padding: 14px 16px; background: #1e1e2e; border: 2px solid rgba(255,255,255,0.2); border-radius: 10px; color: #fff; font-size: 15px;">
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 18px;">
|
||||
<label style="display: block; font-size: 12px; opacity: 0.8; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 1px;">任务描述</label>
|
||||
<textarea id="inlineDesc" placeholder="输入详细描述..." style="width: 100%; padding: 14px 16px; background: #1e1e2e; border: 2px solid rgba(255,255,255,0.2); border-radius: 10px; color: #fff; font-size: 14px; resize: vertical; min-height: 60px;"></textarea>
|
||||
</div>
|
||||
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin-bottom: 18px;">
|
||||
<div>
|
||||
<label style="display: block; font-size: 12px; opacity: 0.8; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 1px;">开始日期</label>
|
||||
<input type="date" id="inlineStart" style="width: 100%; padding: 14px 16px; background: #1e1e2e; border: 2px solid rgba(255,255,255,0.2); border-radius: 10px; color: #fff; font-size: 15px;">
|
||||
</div>
|
||||
<div>
|
||||
<label style="display: block; font-size: 12px; opacity: 0.8; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 1px;">截止日期</label>
|
||||
<input type="date" id="inlineEnd" style="width: 100%; padding: 14px 16px; background: #1e1e2e; border: 2px solid rgba(255,255,255,0.2); border-radius: 10px; color: #fff; font-size: 15px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 18px;">
|
||||
<label style="display: block; font-size: 12px; opacity: 0.8; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 1px;">时间</label>
|
||||
<input type="time" id="inlineTime" value="09:00" style="width: 100%; padding: 14px 16px; background: #1e1e2e; border: 2px solid rgba(255,255,255,0.2); border-radius: 10px; color: #fff; font-size: 15px;">
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 18px;">
|
||||
<label style="display: block; font-size: 12px; opacity: 0.8; margin-bottom: 10px; text-transform: uppercase; letter-spacing: 1px;">优先级</label>
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<label style="flex: 1; text-align: center; padding: 12px; background: rgba(254, 215, 215, 0.2); border: 2px solid transparent; border-radius: 10px; cursor: pointer; transition: all 0.3s;" onclick="selectPriority(this, 'high')">
|
||||
🔴 高
|
||||
<input type="radio" name="priority" value="high" checked style="display: none;">
|
||||
</label>
|
||||
<label style="flex: 1; text-align: center; padding: 12px; background: rgba(254, 235, 200, 0.2); border: 2px solid transparent; border-radius: 10px; cursor: pointer; transition: all 0.3s;" onclick="selectPriority(this, 'medium')">
|
||||
🟡 中
|
||||
<input type="radio" name="priority" value="medium" style="display: none;">
|
||||
</label>
|
||||
<label style="flex: 1; text-align: center; padding: 12px; background: rgba(198, 246, 213, 0.2); border: 2px solid transparent; border-radius: 10px; cursor: pointer; transition: all 0.3s;" onclick="selectPriority(this, 'low')">
|
||||
🟢 低
|
||||
<input type="radio" name="priority" value="low" style="display: none;">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 20px;">
|
||||
<label style="display: block; font-size: 12px; opacity: 0.8; margin-bottom: 10px; text-transform: uppercase; letter-spacing: 1px;">分类</label>
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||
<label style="padding: 10px 16px; background: rgba(233, 216, 253, 0.2); border: 2px solid #667eea; border-radius: 20px; cursor: pointer; font-size: 13px;" onclick="selectCategory(this, 'work')">
|
||||
💼 工作
|
||||
<input type="radio" name="category" value="work" checked style="display: none;">
|
||||
</label>
|
||||
<label style="padding: 10px 16px; background: rgba(190, 227, 248, 0.2); border: 2px solid transparent; border-radius: 20px; cursor: pointer; font-size: 13px;" onclick="selectCategory(this, 'life')">
|
||||
🏠 生活
|
||||
<input type="radio" name="category" value="life" style="display: none;">
|
||||
</label>
|
||||
<label style="padding: 10px 16px; background: rgba(254, 215, 226, 0.2); border: 2px solid transparent; border-radius: 20px; cursor: pointer; font-size: 13px;" onclick="selectCategory(this, 'study')">
|
||||
📚 学习
|
||||
<input type="radio" name="category" value="study" style="display: none;">
|
||||
</label>
|
||||
<label style="padding: 10px 16px; background: rgba(198, 246, 213, 0.2); border: 2px solid transparent; border-radius: 20px; cursor: pointer; font-size: 13px;" onclick="selectCategory(this, 'health')">
|
||||
💪 健康
|
||||
<input type="radio" name="category" value="health" style="display: none;">
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 12px;">
|
||||
<button type="button" onclick="clearInlineForm()" style="flex: 1; padding: 14px; background: rgba(255,255,255,0.2); border: none; border-radius: 12px; color: white; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.3s;">
|
||||
清空
|
||||
</button>
|
||||
<button type="button" onclick="submitInlineForm()" style="flex: 2; padding: 14px; background: white; border: none; border-radius: 12px; color: #667eea; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.3s;">
|
||||
✅ 添加任务
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
---
|
||||
|
||||
**✅ 您现在可以直接在上方的卡片中填写任务信息,点击"添加任务"即可!**
|
||||
|
||||
填写完成后告诉我"已填好"或"添加",我会帮您确认是否成功!
|
||||
`
|
||||
|
||||
// 初始化事件
|
||||
// initForm()
|
||||
})
|
||||
|
||||
const initForm = () => {
|
||||
setTimeout(() => {
|
||||
const today = new Date().toISOString().split('T')[0]
|
||||
const startInput = document.getElementById('inlineStart')
|
||||
const endInput = document.getElementById('inlineEnd')
|
||||
if (startInput) startInput.value = today
|
||||
if (endInput) endInput.value = today
|
||||
|
||||
const clearBtn = document.getElementById('clearBtn')
|
||||
const submitBtn = document.getElementById('submitBtn')
|
||||
|
||||
if (clearBtn) {
|
||||
clearBtn.onclick = () => {
|
||||
const titleInput = document.getElementById('inlineTitle')
|
||||
const descInput = document.getElementById('inlineDesc')
|
||||
if (titleInput) titleInput.value = ''
|
||||
if (descInput) descInput.value = ''
|
||||
}
|
||||
}
|
||||
|
||||
if (submitBtn) {
|
||||
submitBtn.onclick = () => {
|
||||
const titleInput = document.getElementById('inlineTitle')
|
||||
const title = titleInput?.value.trim()
|
||||
if (!title) {
|
||||
alert('请输入任务名称')
|
||||
return
|
||||
}
|
||||
alert(`任务已添加:${title}`)
|
||||
if (titleInput) titleInput.value = ''
|
||||
if (document.getElementById('inlineDesc')) document.getElementById('inlineDesc').value = ''
|
||||
}
|
||||
}
|
||||
}, 100)
|
||||
}
|
||||
</script>
|
||||
538
pages/text/text2.vue
Normal file
538
pages/text/text2.vue
Normal file
@@ -0,0 +1,538 @@
|
||||
<template>
|
||||
<view class="container">
|
||||
<!-- 连接状态卡片 -->
|
||||
<view class="status-card">
|
||||
<text class="status-label">连接状态</text>
|
||||
<view class="status-value" :class="connectionStatusClass">
|
||||
<text>{{ socketStore.getStatusText() }}</text>
|
||||
</view>
|
||||
<view class="status-info" v-if="socketStore.reconnectAttempts > 0">
|
||||
<text class="reconnect-info">重连次数: {{ socketStore.reconnectAttempts }}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 配置区域 -->
|
||||
<view class="config-card">
|
||||
<text class="section-title">连接配置</text>
|
||||
<view class="input-group">
|
||||
<text class="input-label">Token</text>
|
||||
<input class="input-field" v-model="config.token" placeholder="请输入Token" />
|
||||
</view>
|
||||
<view class="input-group">
|
||||
<text class="input-label">Conversation ID</text>
|
||||
<input class="input-field" v-model="config.conversationId" placeholder="请输入会话ID(可选)" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 连接控制 -->
|
||||
<view class="control-card">
|
||||
<button class="btn btn-primary" :disabled="socketStore.isConnected || socketStore.isConnecting"
|
||||
@click="handleConnect">
|
||||
{{ socketStore.isConnecting ? '连接中...' : '连接' }}
|
||||
</button>
|
||||
<button class="btn btn-danger" :disabled="!socketStore.isConnected" @click="handleDisconnect">
|
||||
断开
|
||||
</button>
|
||||
<button class="btn btn-secondary" @click="handleSendPing" :disabled="!socketStore.isConnected">
|
||||
Ping
|
||||
</button>
|
||||
</view>
|
||||
|
||||
<!-- 发送消息区域 -->
|
||||
<view class="send-card">
|
||||
<text class="section-title">发送消息</text>
|
||||
<view class="send-input-wrapper">
|
||||
<textarea class="send-input" v-model="sendMessage" placeholder="输入要发送的消息(JSON格式)"
|
||||
:disabled="!socketStore.isConnected"></textarea>
|
||||
<view class="send-buttons">
|
||||
<button class="btn btn-primary btn-small"
|
||||
:disabled="!socketStore.isConnected || !sendMessage.trim()" @click="handleSend">
|
||||
发送
|
||||
</button>
|
||||
<button class="btn btn-outline btn-small" :disabled="!sendMessage.trim()" @click="formatJson">
|
||||
格式化
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 快捷消息 -->
|
||||
<view class="quick-messages">
|
||||
<text class="quick-label">快捷消息:</text>
|
||||
<view class="quick-btns">
|
||||
<button v-for="item in quickMessages" :key="item.label" class="quick-btn"
|
||||
:disabled="!socketStore.isConnected" @click="sendQuickMessage(item.data)">
|
||||
{{ item.label }}
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 日志区域 -->
|
||||
<view class="log-card">
|
||||
<view class="log-header">
|
||||
<view class="log-title-wrapper">
|
||||
<text class="section-title">日志</text>
|
||||
<text class="log-count">({{ socketStore.logs.length }})</text>
|
||||
</view>
|
||||
<view class="log-actions">
|
||||
<button class="btn btn-small btn-secondary" @click="socketStore.clearLogs">清空</button>
|
||||
</view>
|
||||
</view>
|
||||
<scroll-view class="log-list" scroll-y @scrolltoupper="loadMoreLogs">
|
||||
<view v-for="(log, index) in socketStore.logs" :key="index" class="log-item" :class="'log-' + log.type">
|
||||
<text class="log-time">{{ log.time }}</text>
|
||||
<text class="log-content">{{ log.content }}</text>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
ref,
|
||||
computed,
|
||||
onMounted,
|
||||
onUnmounted,
|
||||
watch
|
||||
} from 'vue'
|
||||
import {
|
||||
useSocketStore
|
||||
} from '@/stores/socket.js'
|
||||
import {
|
||||
getToken,
|
||||
getTaskCallId,
|
||||
getCurrentSessionId
|
||||
} from '@/utils/user-info.js'
|
||||
|
||||
// Store
|
||||
const socketStore = useSocketStore()
|
||||
|
||||
// 响应式数据
|
||||
const config = ref({
|
||||
token: getToken(),
|
||||
conversationId: getCurrentSessionId()
|
||||
})
|
||||
const sendMessage = ref('')
|
||||
const quickMessages = ref([{
|
||||
label: 'Ping',
|
||||
data: {
|
||||
ws_event: 'ping'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '认证',
|
||||
data: {
|
||||
ws_event: 'auth',
|
||||
data: {
|
||||
token: '',
|
||||
conversation_id: ''
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
label: '测试消息',
|
||||
data: {
|
||||
ws_event: 'message',
|
||||
data: {
|
||||
task_call_id: getTaskCallId(),
|
||||
token: getToken(),
|
||||
conversation_id: getCurrentSessionId()
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
// 计算属性
|
||||
const connectionStatusClass = computed(() => {
|
||||
const status = socketStore.connectionStatus
|
||||
return {
|
||||
'status-connected': status === 'connected',
|
||||
'status-connecting': status === 'connecting',
|
||||
'status-error': status === 'error',
|
||||
'status-disconnected': status === 'disconnected'
|
||||
}
|
||||
})
|
||||
|
||||
// 方法
|
||||
const handleConnect = () => {
|
||||
if (!config.value.token) {
|
||||
uni.showToast({
|
||||
title: '请输入Token',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
socketStore.connect({
|
||||
token: config.value.token,
|
||||
conversationId: getCurrentSessionId()
|
||||
})
|
||||
}
|
||||
|
||||
const handleDisconnect = () => {
|
||||
socketStore.disconnect()
|
||||
}
|
||||
|
||||
const handleSendPing = async () => {
|
||||
try {
|
||||
await socketStore.sendPing()
|
||||
} catch (e) {
|
||||
uni.showToast({
|
||||
title: '发送失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleSend = async () => {
|
||||
if (!sendMessage.value.trim()) return
|
||||
|
||||
try {
|
||||
const messageData = {
|
||||
ws_event: 'message',
|
||||
data: {
|
||||
task_call_id: getTaskCallId(),
|
||||
result: {
|
||||
tools: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await socketStore.send(messageData)
|
||||
sendMessage.value = ''
|
||||
} catch (e) {
|
||||
uni.showToast({
|
||||
title: '发送失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const sendQuickMessage = async (template) => {
|
||||
// console.log('getCurrentSessionId:', getCurrentSessionId())
|
||||
|
||||
const messageData = {
|
||||
ws_event: 'message',
|
||||
data: {
|
||||
task_call_id: getTaskCallId(),
|
||||
token: getToken(),
|
||||
conversation_id: getCurrentSessionId()
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
await socketStore.send(messageData)
|
||||
} catch (e) {
|
||||
uni.showToast({
|
||||
title: '发送失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const formatJson = () => {
|
||||
if (!sendMessage.value.trim()) return
|
||||
|
||||
try {
|
||||
const obj = JSON.parse(sendMessage.value)
|
||||
sendMessage.value = JSON.stringify(obj, null, 2)
|
||||
} catch {
|
||||
uni.showToast({
|
||||
title: '不是有效的JSON',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const loadMoreLogs = () => {
|
||||
// 预留扩展
|
||||
}
|
||||
watch(() => socketStore.isDisconnected, (newVal) => {
|
||||
if (newVal && socketStore.messageString) {
|
||||
console.log(socketStore.messageString);
|
||||
}
|
||||
})
|
||||
|
||||
// 生命周期
|
||||
onMounted(() => {
|
||||
socketStore.addLog('info', '=== Socket测试页面 ===')
|
||||
socketStore.addLog('info', '页面已加载')
|
||||
|
||||
if (socketStore.isConnected) {
|
||||
socketStore.addLog('info', '当前已处于连接状态')
|
||||
}
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
socketStore.addLog('info', '页面卸载')
|
||||
// 注意:这里不主动断开连接,由App级别管理
|
||||
})
|
||||
</script>
|
||||
<style scoped>
|
||||
.container {
|
||||
padding: 20rpx;
|
||||
background-color: #f5f5f5;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.status-card,
|
||||
.config-card,
|
||||
.control-card,
|
||||
.send-card,
|
||||
.log-card {
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
|
||||
.status-label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.status-value {
|
||||
display: inline-block;
|
||||
padding: 12rpx 32rpx;
|
||||
border-radius: 8rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.status-connected {
|
||||
background-color: #e6f7e6;
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.status-connecting {
|
||||
background-color: #fff7e6;
|
||||
color: #faad14;
|
||||
}
|
||||
|
||||
.status-disconnected {
|
||||
background-color: #f5f5f5;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.status-error {
|
||||
background-color: #fff1f0;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.status-info {
|
||||
margin-top: 16rpx;
|
||||
}
|
||||
|
||||
.reconnect-info {
|
||||
font-size: 24rpx;
|
||||
color: #faad14;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 32rpx;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 24rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.input-group {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
font-size: 28rpx;
|
||||
color: #666;
|
||||
margin-bottom: 12rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.input-field {
|
||||
border: 2rpx solid #e8e8e8;
|
||||
border-radius: 8rpx;
|
||||
padding: 20rpx;
|
||||
font-size: 28rpx;
|
||||
background-color: #fafafa;
|
||||
}
|
||||
|
||||
.control-card {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.btn {
|
||||
flex: 1;
|
||||
height: 88rpx;
|
||||
line-height: 88rpx;
|
||||
border-radius: 12rpx;
|
||||
font-size: 32rpx;
|
||||
text-align: center;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #1890ff;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background-color: #ff4d4f;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background-color: #f0f0f0;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.btn-outline {
|
||||
background-color: transparent;
|
||||
color: #1890ff;
|
||||
border: 2rpx solid #1890ff;
|
||||
}
|
||||
|
||||
.btn-small {
|
||||
height: 64rpx;
|
||||
line-height: 64rpx;
|
||||
font-size: 26rpx;
|
||||
padding: 0 24rpx;
|
||||
}
|
||||
|
||||
.btn[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.send-input-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.send-input {
|
||||
border: 2rpx solid #e8e8e8;
|
||||
border-radius: 8rpx;
|
||||
padding: 20rpx;
|
||||
font-size: 28rpx;
|
||||
min-height: 160rpx;
|
||||
background-color: #fafafa;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.send-buttons {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
}
|
||||
|
||||
.quick-messages {
|
||||
margin-top: 24rpx;
|
||||
padding-top: 24rpx;
|
||||
border-top: 2rpx solid #f0f0f0;
|
||||
}
|
||||
|
||||
.quick-label {
|
||||
font-size: 26rpx;
|
||||
color: #999;
|
||||
margin-bottom: 16rpx;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.quick-btns {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.quick-btn {
|
||||
padding: 12rpx 24rpx;
|
||||
background-color: #f0f5ff;
|
||||
color: #1890ff;
|
||||
border-radius: 8rpx;
|
||||
font-size: 26rpx;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.quick-btn[disabled] {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.log-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.log-title-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.log-title-wrapper .section-title {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.log-count {
|
||||
font-size: 24rpx;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.log-actions {
|
||||
display: flex;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.log-list {
|
||||
max-height: 500rpx;
|
||||
background-color: #1e1e1e;
|
||||
border-radius: 8rpx;
|
||||
padding: 20rpx;
|
||||
}
|
||||
|
||||
.log-item {
|
||||
display: flex;
|
||||
margin-bottom: 12rpx;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 24rpx;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.log-time {
|
||||
color: #888;
|
||||
margin-right: 16rpx;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.log-content {
|
||||
word-break: break-all;
|
||||
flex: 1;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.log-info .log-content {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.log-success .log-content {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
.log-error .log-content {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
.log-warn .log-content {
|
||||
color: #faad14;
|
||||
}
|
||||
|
||||
.log-send .log-content {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.log-receive .log-content {
|
||||
color: #52c41a;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user