collabdocchat 2.5.1 → 2.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import"emoji-picker-element";(function(){const a=document.createElement("link").relList;if(a&&a.supports&&a.supports("modulepreload"))return;for(const z of document.querySelectorAll('link[rel="modulepreload"]'))d(z);new MutationObserver(z=>{for(const D of z)if(D.type==="childList")for(const p of D.addedNodes)p.tagName==="LINK"&&p.rel==="modulepreload"&&d(p)}).observe(document,{childList:!0,subtree:!0});function y(z){const D={};return z.integrity&&(D.integrity=z.integrity),z.referrerPolicy&&(D.referrerPolicy=z.referrerPolicy),z.crossOrigin==="use-credentials"?D.credentials="include":z.crossOrigin==="anonymous"?D.credentials="omit":D.credentials="same-origin",D}function d(z){if(z.ep)return;z.ep=!0;const D=y(z);fetch(z.href,D)}})();const me="http://localhost:8765/api";class oe{async login(a,y){const d=await fetch(`${me}/auth/login`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:a,password:y})});if(!d.ok){const z=await d.json();throw new Error(z.message)}return await d.json()}async register(a,y){const d=await fetch(`${me}/auth/register`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:a,password:y})});if(!d.ok){const z=await d.json();throw new Error(z.message)}return await d.json()}async getCurrentUser(){const a=localStorage.getItem("token"),y=await fetch(`${me}/auth/me`,{headers:{Authorization:`Bearer ${a}`}});if(!y.ok)throw new Error("获取用户信息失败");return(await y.json()).user}logout(){localStorage.removeItem("token"),window.location.reload()}}class he{constructor(){this.ws=null,this.listeners=new Map}connect(a){this.ws=new WebSocket("ws://localhost:8765"),this.ws.onopen=()=>{console.log("✅ WebSocket 连接成功"),this.send({type:"auth",token:a})},this.ws.onmessage=y=>{const d=JSON.parse(y.data);this.notifyListeners(d.type,d)},this.ws.onerror=y=>{console.error("❌ WebSocket 错误:",y)},this.ws.onclose=()=>{console.log("🔌 WebSocket 连接关闭"),setTimeout(()=>this.connect(a),3e3)}}send(a){this.ws&&this.ws.readyState===WebSocket.OPEN&&this.ws.send(JSON.stringify(a))}on(a,y){this.listeners.has(a)||this.listeners.set(a,[]),this.listeners.get(a).push(y)}off(a,y){if(this.listeners.has(a)){const d=this.listeners.get(a),z=d.indexOf(y);z>-1&&d.splice(z,1)}}notifyListeners(a,y){this.listeners.has(a)&&this.listeners.get(a).forEach(d=>d(y))}joinGroup(a){this.send({type:"join_group",groupId:a})}sendChatMessage(a,y,d){this.send({type:"chat_message",groupId:a,username:y,content:d})}syncDocument(a,y,d){this.send({type:"document_sync",documentId:a,content:y,cursorPosition:d})}respondToCall(a,y){this.send({type:"call_response",groupId:a,username:y})}sendTyping(a,y,d){this.send({type:"typing",documentId:a,username:y,isTyping:d})}sendWhiteboardDraw(a,y){this.send({type:"whiteboard_draw",groupId:a,...y})}sendWhiteboardClear(a){this.send({type:"whiteboard_clear",groupId:a})}}function fe(_){const a=document.getElementById("app"),y=new oe;a.innerHTML=`
1
+ (function(){const e=document.createElement("link").relList;if(e&&e.supports&&e.supports("modulepreload"))return;for(const l of document.querySelectorAll('link[rel="modulepreload"]'))a(l);new MutationObserver(l=>{for(const o of l)if(o.type==="childList")for(const i of o.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&a(i)}).observe(document,{childList:!0,subtree:!0});function n(l){const o={};return l.integrity&&(o.integrity=l.integrity),l.referrerPolicy&&(o.referrerPolicy=l.referrerPolicy),l.crossOrigin==="use-credentials"?o.credentials="include":l.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function a(l){if(l.ep)return;l.ep=!0;const o=n(l);fetch(l.href,o)}})();const Me="http://localhost:8765/api";class Be{async login(e,n){const a=await fetch(`${Me}/auth/login`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:e,password:n})});if(!a.ok){const l=await a.json();throw new Error(l.message)}return await a.json()}async register(e,n){const a=await fetch(`${Me}/auth/register`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:e,password:n})});if(!a.ok){const l=await a.json();throw new Error(l.message)}return await a.json()}async getCurrentUser(){const e=localStorage.getItem("token"),n=await fetch(`${Me}/auth/me`,{headers:{Authorization:`Bearer ${e}`}});if(!n.ok)throw new Error("获取用户信息失败");return(await n.json()).user}logout(){localStorage.removeItem("token"),window.location.reload()}}class Ct{constructor(){this.ws=null,this.listeners=new Map}connect(e){this.ws=new WebSocket("ws://localhost:8765"),this.ws.onopen=()=>{console.log("✅ WebSocket 连接成功"),this.send({type:"auth",token:e})},this.ws.onmessage=n=>{const a=JSON.parse(n.data);this.notifyListeners(a.type,a)},this.ws.onerror=n=>{console.error("❌ WebSocket 错误:",n)},this.ws.onclose=()=>{console.log("🔌 WebSocket 连接关闭"),setTimeout(()=>this.connect(e),3e3)}}send(e){this.ws&&this.ws.readyState===WebSocket.OPEN&&this.ws.send(JSON.stringify(e))}on(e,n){this.listeners.has(e)||this.listeners.set(e,[]),this.listeners.get(e).push(n)}off(e,n){if(this.listeners.has(e)){const a=this.listeners.get(e),l=a.indexOf(n);l>-1&&a.splice(l,1)}}notifyListeners(e,n){this.listeners.has(e)&&this.listeners.get(e).forEach(a=>a(n))}joinGroup(e){this.send({type:"join_group",groupId:e})}sendChatMessage(e,n,a){this.send({type:"chat_message",groupId:e,username:n,content:a})}syncDocument(e,n,a){this.send({type:"document_sync",documentId:e,content:n,cursorPosition:a})}respondToCall(e,n){this.send({type:"call_response",groupId:e,username:n})}sendTyping(e,n,a){this.send({type:"typing",documentId:e,username:n,isTyping:a})}sendWhiteboardDraw(e,n){this.send({type:"whiteboard_draw",groupId:e,...n})}sendWhiteboardClear(e){this.send({type:"whiteboard_clear",groupId:e})}}function zt(t){const e=document.getElementById("app"),n=new Be;e.innerHTML=`
2
2
  <div class="login-container">
3
3
  <div class="login-card">
4
4
  <div class="login-header">
@@ -48,7 +48,10 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
48
48
  </div>
49
49
  </div>
50
50
  </div>
51
- `,document.getElementById("showRegister").addEventListener("click",()=>{document.getElementById("loginSection").classList.add("hidden"),document.getElementById("registerSection").classList.remove("hidden")}),document.getElementById("showLogin").addEventListener("click",()=>{document.getElementById("registerSection").classList.add("hidden"),document.getElementById("loginSection").classList.remove("hidden")}),document.getElementById("loginForm").addEventListener("submit",async d=>{d.preventDefault();const z=new FormData(d.target),D=z.get("username"),p=z.get("password");try{const R=await y.login(D,p);_(R.user,R.token)}catch(R){document.getElementById("loginError").textContent=R.message}}),document.getElementById("registerForm").addEventListener("submit",async d=>{d.preventDefault();const z=new FormData(d.target),D=z.get("username"),p=z.get("password");try{const R=await y.register(D,p);_(R.user,R.token)}catch(R){document.getElementById("registerError").textContent=R.message}})}const ue="http://localhost:8765/api";class be{constructor(){this.token=localStorage.getItem("token")}async request(a,y={}){const d=await fetch(`${ue}${a}`,{...y,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.token}`,...y.headers}});if(!d.ok){const z=await d.json().catch(()=>({message:"请求失败"}));throw console.error("API 错误:",{endpoint:a,status:d.status,error:z}),new Error(z.message||`请求失败: ${d.status}`)}return await d.json()}async getGroups(){return await this.request("/groups")}async getAllGroups(){return await this.request("/groups/all")}async getGroup(a){return await this.request(`/groups/${a}`)}async createGroup(a,y,d){return await this.request("/groups",{method:"POST",body:JSON.stringify({name:a,description:y,members:d})})}async joinGroup(a){return await this.request(`/groups/${a}/join`,{method:"POST"})}async leaveGroup(a){return await this.request(`/groups/${a}/leave`,{method:"POST"})}async addMember(a,y){return await this.request(`/groups/${a}/members`,{method:"POST",body:JSON.stringify({userId:y})})}async removeMember(a,y){return await this.request(`/groups/${a}/members/${y}`,{method:"DELETE"})}async setMuteAll(a,y){return await this.request(`/groups/${a}/mute/all`,{method:"POST",body:JSON.stringify({enabled:y})})}async setUserMute(a,y,d){return await this.request(`/groups/${a}/mute/users/${y}`,{method:"POST",body:JSON.stringify({muted:d})})}async getAllUsers(){return await this.request("/auth/users")}async getGroupMessages(a){return await this.request(`/groups/${a}/messages`)}async randomCall(a,y=1){return await this.request(`/groups/${a}/call`,{method:"POST",body:JSON.stringify({count:y})})}async clearGroupMessages(a){return await this.request(`/groups/${a}/messages`,{method:"DELETE",body:JSON.stringify({deleteAll:!0})})}async getTasks(a){return await this.request(`/tasks/group/${a}`)}async getMyTasks(){return await this.request("/tasks/my")}async createTask(a){return await this.request("/tasks",{method:"POST",body:JSON.stringify(a)})}async updateTaskStatus(a,y){return await this.request(`/tasks/${a}/status`,{method:"PATCH",body:JSON.stringify({status:y})})}async deleteTask(a){return await this.request(`/tasks/${a}`,{method:"DELETE"})}async getDocuments(a){return await this.request(`/documents/group/${a}`)}async getDocument(a){return await this.request(`/documents/${a}`)}async createDocument(a,y,d,z=[]){return await this.request("/documents",{method:"POST",body:JSON.stringify({title:a,content:y,groupId:d,editableMembers:z})})}async updateDocument(a,y){return await this.request(`/documents/${a}`,{method:"PATCH",body:JSON.stringify({content:y})})}async updateDocumentPermissions(a,y){return await this.request(`/documents/${a}/permissions`,{method:"PATCH",body:JSON.stringify({editableMembers:y})})}async getDocumentVersions(a){return await this.request(`/documents/${a}/versions`)}async deleteDocument(a){return await this.request(`/documents/${a}`,{method:"DELETE"})}async getAuditLogs(a={},y={}){const d=new URLSearchParams;Object.keys(a).forEach(D=>{a[D]&&d.append(D,a[D])}),Object.keys(y).forEach(D=>{y[D]&&d.append(D,y[D])});const z=d.toString();return await this.request(`/audit${z?"?"+z:""}`)}async getUserActivityStats(a,y={}){const z=new URLSearchParams(y).toString();return await this.request(`/audit/user-stats/${a}${z?"?"+z:""}`)}async getDocumentEditHistory(a,y=20){return await this.request(`/audit/document-history/${a}?limit=${y}`)}async getGroupAuditLogs(a,y={},d={}){const z=new URLSearchParams;Object.keys(y).forEach(p=>{y[p]&&z.append(p,y[p])}),Object.keys(d).forEach(p=>{d[p]&&z.append(p,d[p])});const D=z.toString();return await this.request(`/audit/group/${a}${D?"?"+D:""}`)}async getAuditSummary(a={}){const d=new URLSearchParams(a).toString();return await this.request(`/audit/stats/summary${d?"?"+d:""}`)}async getAuditLogDetail(a){return await this.request(`/audit/${a}`)}async clearAuditLogs(a={}){return await this.request("/audit",{method:"DELETE",body:JSON.stringify(a)})}async uploadFile(a,y,d=""){const z=new FormData;z.append("file",y),z.append("groupId",a),d&&z.append("description",d);const D=await fetch(`${ue}/files/upload`,{method:"POST",headers:{Authorization:`Bearer ${this.token}`},body:z});if(!D.ok){const p=await D.json().catch(()=>({message:"上传失败"}));throw new Error(p.message||"上传失败")}return await D.json()}async getGroupFiles(a){return await this.request(`/files/group/${a}`)}async deleteFile(a){return await this.request(`/files/${a}`,{method:"DELETE"})}getFileDownloadUrl(a){return`${ue}/files/${a}/download?token=${this.token}`}async createPoll(a){return await this.request("/polls",{method:"POST",body:JSON.stringify(a)})}async getGroupPolls(a){return await this.request(`/polls/group/${a}`)}async getPoll(a){return await this.request(`/polls/${a}`)}async vote(a,y){return await this.request(`/polls/${a}/vote`,{method:"POST",body:JSON.stringify({optionIndexes:y})})}async endPoll(a){return await this.request(`/polls/${a}/end`,{method:"PUT"})}async deletePoll(a){return await this.request(`/polls/${a}`,{method:"DELETE"})}}function we(_,a){const y=document.getElementById("app"),d=new be,z=new oe,D=_.id||_._id;let p=null,R=[];const se=localStorage.getItem("currentTheme")||"dark";ke(se),y.innerHTML=`
51
+ `,document.getElementById("showRegister").addEventListener("click",()=>{document.getElementById("loginSection").classList.add("hidden"),document.getElementById("registerSection").classList.remove("hidden")}),document.getElementById("showLogin").addEventListener("click",()=>{document.getElementById("registerSection").classList.add("hidden"),document.getElementById("loginSection").classList.remove("hidden")}),document.getElementById("loginForm").addEventListener("submit",async a=>{a.preventDefault();const l=new FormData(a.target),o=l.get("username"),i=l.get("password");try{const S=await n.login(o,i);t(S.user,S.token)}catch(S){document.getElementById("loginError").textContent=S.message}}),document.getElementById("registerForm").addEventListener("submit",async a=>{a.preventDefault();const l=new FormData(a.target),o=l.get("username"),i=l.get("password");try{const S=await n.register(o,i);t(S.user,S.token)}catch(S){document.getElementById("registerError").textContent=S.message}})}const Ce="http://localhost:8765/api";class ct{constructor(){this.token=localStorage.getItem("token")}async request(e,n={}){const a=await fetch(`${Ce}${e}`,{...n,headers:{"Content-Type":"application/json",Authorization:`Bearer ${this.token}`,...n.headers}});if(!a.ok){const l=await a.json().catch(()=>({message:"请求失败"}));throw console.error("API 错误:",{endpoint:e,status:a.status,error:l}),new Error(l.message||`请求失败: ${a.status}`)}return await a.json()}async getGroups(){return await this.request("/groups")}async getAllGroups(){return await this.request("/groups/all")}async getGroup(e){return await this.request(`/groups/${e}`)}async createGroup(e,n,a){return await this.request("/groups",{method:"POST",body:JSON.stringify({name:e,description:n,members:a})})}async joinGroup(e){return await this.request(`/groups/${e}/join`,{method:"POST"})}async leaveGroup(e){return await this.request(`/groups/${e}/leave`,{method:"POST"})}async addMember(e,n){return await this.request(`/groups/${e}/members`,{method:"POST",body:JSON.stringify({userId:n})})}async removeMember(e,n){return await this.request(`/groups/${e}/members/${n}`,{method:"DELETE"})}async setMuteAll(e,n){return await this.request(`/groups/${e}/mute/all`,{method:"POST",body:JSON.stringify({enabled:n})})}async setUserMute(e,n,a){return await this.request(`/groups/${e}/mute/users/${n}`,{method:"POST",body:JSON.stringify({muted:a})})}async getAllUsers(){return await this.request("/auth/users")}async getGroupMessages(e){return await this.request(`/groups/${e}/messages`)}async randomCall(e,n=1){return await this.request(`/groups/${e}/call`,{method:"POST",body:JSON.stringify({count:n})})}async clearGroupMessages(e){return await this.request(`/groups/${e}/messages`,{method:"DELETE",body:JSON.stringify({deleteAll:!0})})}async getTasks(e){return await this.request(`/tasks/group/${e}`)}async getMyTasks(){return await this.request("/tasks/my")}async createTask(e){return await this.request("/tasks",{method:"POST",body:JSON.stringify(e)})}async updateTaskStatus(e,n){return await this.request(`/tasks/${e}/status`,{method:"PATCH",body:JSON.stringify({status:n})})}async deleteTask(e){return await this.request(`/tasks/${e}`,{method:"DELETE"})}async getDocuments(e){return await this.request(`/documents/group/${e}`)}async getDocument(e){return await this.request(`/documents/${e}`)}async createDocument(e,n,a,l=[]){return await this.request("/documents",{method:"POST",body:JSON.stringify({title:e,content:n,groupId:a,editableMembers:l})})}async updateDocument(e,n){return await this.request(`/documents/${e}`,{method:"PATCH",body:JSON.stringify({content:n})})}async updateDocumentPermissions(e,n){return await this.request(`/documents/${e}/permissions`,{method:"PATCH",body:JSON.stringify({editableMembers:n})})}async getDocumentVersions(e){return await this.request(`/documents/${e}/versions`)}async deleteDocument(e){return await this.request(`/documents/${e}`,{method:"DELETE"})}async getAuditLogs(e={},n={}){const a=new URLSearchParams;Object.keys(e).forEach(o=>{e[o]&&a.append(o,e[o])}),Object.keys(n).forEach(o=>{n[o]&&a.append(o,n[o])});const l=a.toString();return await this.request(`/audit${l?"?"+l:""}`)}async getUserActivityStats(e,n={}){const l=new URLSearchParams(n).toString();return await this.request(`/audit/user-stats/${e}${l?"?"+l:""}`)}async getDocumentEditHistory(e,n=20){return await this.request(`/audit/document-history/${e}?limit=${n}`)}async getGroupAuditLogs(e,n={},a={}){const l=new URLSearchParams;Object.keys(n).forEach(i=>{n[i]&&l.append(i,n[i])}),Object.keys(a).forEach(i=>{a[i]&&l.append(i,a[i])});const o=l.toString();return await this.request(`/audit/group/${e}${o?"?"+o:""}`)}async getAuditSummary(e={}){const a=new URLSearchParams(e).toString();return await this.request(`/audit/stats/summary${a?"?"+a:""}`)}async getAuditLogDetail(e){return await this.request(`/audit/${e}`)}async clearAuditLogs(e={}){return await this.request("/audit",{method:"DELETE",body:JSON.stringify(e)})}async uploadFile(e,n,a=""){const l=new FormData;l.append("file",n),l.append("groupId",e),a&&l.append("description",a);const o=await fetch(`${Ce}/files/upload`,{method:"POST",headers:{Authorization:`Bearer ${this.token}`},body:l});if(!o.ok){const i=await o.json().catch(()=>({message:"上传失败"}));throw new Error(i.message||"上传失败")}return await o.json()}async getGroupFiles(e){return await this.request(`/files/group/${e}`)}async deleteFile(e){return await this.request(`/files/${e}`,{method:"DELETE"})}getFileDownloadUrl(e){return`${Ce}/files/${e}/download?token=${this.token}`}async createPoll(e){return await this.request("/polls",{method:"POST",body:JSON.stringify(e)})}async getGroupPolls(e){return await this.request(`/polls/group/${e}`)}async getPoll(e){return await this.request(`/polls/${e}`)}async vote(e,n){return await this.request(`/polls/${e}/vote`,{method:"POST",body:JSON.stringify({optionIndexes:n})})}async endPoll(e){return await this.request(`/polls/${e}/end`,{method:"PUT"})}async deletePoll(e){return await this.request(`/polls/${e}`,{method:"DELETE"})}}function we(t){if(typeof t!="string"||!t)throw new Error("expected a non-empty string, got: "+t)}function ze(t){if(typeof t!="number")throw new Error("expected a number, got: "+t)}const jt=1,Dt=1,me="emoji",he="keyvalue",Ge="favorites",_t="tokens",pt="tokens",At="unicode",ut="count",Pt="group",Ft="order",gt="group-order",He="eTag",Te="url",Qe="skinTone",be="readonly",We="readwrite",mt="skinUnicodes",Ot="skinUnicodes",Ht="https://cdn.jsdelivr.net/npm/emoji-picker-element-data@^1/en/emojibase/data.json",Nt="en";function Ut(t,e){const n=new Set,a=[];for(const l of t){const o=e(l);n.has(o)||(n.add(o),a.push(l))}return a}function et(t){return Ut(t,e=>e.unicode)}function Rt(t){function e(n,a,l){const o=a?t.createObjectStore(n,{keyPath:a}):t.createObjectStore(n);if(l)for(const[i,[S,N]]of Object.entries(l))o.createIndex(i,S,{multiEntry:N});return o}e(he),e(me,At,{[pt]:[_t,!0],[gt]:[[Pt,Ft]],[mt]:[Ot,!0]}),e(Ge,void 0,{[ut]:[""]})}const Ne={},Le={},Ie={};function yt(t,e,n){n.onerror=()=>e(n.error),n.onblocked=()=>e(new Error("IDB blocked")),n.onsuccess=()=>t(n.result)}async function qt(t){const e=await new Promise((n,a)=>{const l=indexedDB.open(t,jt);Ne[t]=l,l.onupgradeneeded=o=>{o.oldVersion<Dt&&Rt(l.result)},yt(n,a,l)});return e.onclose=()=>Ve(t),e}function Gt(t){return Le[t]||(Le[t]=qt(t)),Le[t]}function pe(t,e,n,a){return new Promise((l,o)=>{const i=t.transaction(e,n,{durability:"relaxed"}),S=typeof e=="string"?i.objectStore(e):e.map($=>i.objectStore($));let N;a(S,i,$=>{N=$}),i.oncomplete=()=>l(N),i.onerror=()=>o(i.error)})}function Ve(t){const e=Ne[t],n=e&&e.result;if(n){n.close();const a=Ie[t];if(a)for(const l of a)l()}delete Ne[t],delete Le[t],delete Ie[t]}function Wt(t){return new Promise((e,n)=>{Ve(t);const a=indexedDB.deleteDatabase(t);yt(e,n,a)})}function Vt(t,e){let n=Ie[t];n||(n=Ie[t]=[]),n.push(e)}const Kt=new Set([":D","XD",":'D","O:)",":X",":P",";P","XP",":L",":Z",":j","8D","XO","8)",":B",":O",":S",":'o","Dx","X(","D:",":C",">0)",":3","</3","<3","\\M/",":E","8#"]);function ye(t){return t.split(/[\s_]+/).map(e=>!e.match(/\w/)||Kt.has(e)?e.toLowerCase():e.replace(/[)(:,]/g,"").replace(/’/g,"'").toLowerCase()).filter(Boolean)}const Yt=2;function ht(t){return t.filter(Boolean).map(e=>e.toLowerCase()).filter(e=>e.length>=Yt)}function Jt(t){return t.map(({annotation:n,emoticon:a,group:l,order:o,shortcodes:i,skins:S,tags:N,emoji:$,version:D})=>{const H=[...new Set(ht([...(i||[]).map(ye).flat(),...(N||[]).map(ye).flat(),...ye(n),a]))].sort(),_={annotation:n,group:l,order:o,tags:N,tokens:H,unicode:$,version:D};if(a&&(_.emoticon=a),i&&(_.shortcodes=i),S){_.skinTones=[],_.skinUnicodes=[],_.skinVersions=[];for(const{tone:R,emoji:ae,version:Z}of S)_.skinTones.push(R),_.skinUnicodes.push(ae),_.skinVersions.push(Z)}return _})}function bt(t,e,n,a){t[e](n).onsuccess=l=>a&&a(l.target.result)}function ge(t,e,n){bt(t,"get",e,n)}function vt(t,e,n){bt(t,"getAll",e,n)}function Ke(t){t.commit&&t.commit()}function Xt(t,e){let n=t[0];for(let a=1;a<t.length;a++){const l=t[a];e(n)>e(l)&&(n=l)}return n}function xt(t,e){const n=Xt(t,l=>l.length),a=[];for(const l of n)t.some(o=>o.findIndex(i=>e(i)===e(l))===-1)||a.push(l);return a}async function Zt(t){return!await Ye(t,he,Te)}async function Qt(t,e,n){const[a,l]=await Promise.all([He,Te].map(o=>Ye(t,he,o)));return a===n&&l===e}async function ea(t,e){return pe(t,me,be,(a,l,o)=>{let i;const S=()=>{a.getAll(i&&IDBKeyRange.lowerBound(i,!0),50).onsuccess=N=>{const $=N.target.result;for(const D of $)if(i=D.unicode,e(D))return o(D);if($.length<50)return o();S()}};S()})}async function ft(t,e,n,a){try{const l=Jt(e);await pe(t,[me,he],We,([o,i],S)=>{let N,$,D=0;function H(){++D===2&&_()}function _(){if(!(N===a&&$===n)){o.clear();for(const R of l)o.put(R);i.put(a,He),i.put(n,Te),Ke(S)}}ge(i,He,R=>{N=R,H()}),ge(i,Te,R=>{$=R,H()})})}finally{}}async function ta(t,e){return pe(t,me,be,(n,a,l)=>{const o=IDBKeyRange.bound([e,0],[e+1,0],!1,!0);vt(n.index(gt),o,l)})}async function wt(t,e){const n=ht(ye(e));return n.length?pe(t,me,be,(a,l,o)=>{const i=[],S=()=>{i.length===n.length&&N()},N=()=>{const $=xt(i,D=>D.unicode);o($.sort((D,H)=>D.order<H.order?-1:1))};for(let $=0;$<n.length;$++){const D=n[$],H=$===n.length-1?IDBKeyRange.bound(D,D+"￿",!1,!0):IDBKeyRange.only(D);vt(a.index(pt),H,_=>{i.push(_),S()})}}):[]}async function aa(t,e){const n=await wt(t,e);return n.length?n.filter(a=>(a.shortcodes||[]).map(o=>o.toLowerCase()).includes(e.toLowerCase()))[0]||null:await ea(t,l=>(l.shortcodes||[]).includes(e.toLowerCase()))||null}async function na(t,e){return pe(t,me,be,(n,a,l)=>ge(n,e,o=>{if(o)return l(o);ge(n.index(mt),e,i=>l(i||null))}))}function Ye(t,e,n){return pe(t,e,be,(a,l,o)=>ge(a,n,o))}function sa(t,e,n,a){return pe(t,e,We,(l,o)=>{l.put(a,n),Ke(o)})}function oa(t,e){return pe(t,Ge,We,(n,a)=>ge(n,e,l=>{n.put((l||0)+1,e),Ke(a)}))}function ia(t,e,n){return n===0?[]:pe(t,[Ge,me],be,([a,l],o,i)=>{const S=[];a.index(ut).openCursor(void 0,"prev").onsuccess=N=>{const $=N.target.result;if(!$)return i(S);function D(R){if(S.push(R),S.length===n)return i(S);$.continue()}const H=$.primaryKey,_=e.byName(H);if(_)return D(_);ge(l,H,R=>{if(R)return D(R);$.continue()})}})}const ke="";function ra(t,e){const n=new Map;for(const l of t){const o=e(l);for(const i of o){let S=n;for(let $=0;$<i.length;$++){const D=i.charAt($);let H=S.get(D);H||(H=new Map,S.set(D,H)),S=H}let N=S.get(ke);N||(N=[],S.set(ke,N)),N.push(l)}}return(l,o)=>{let i=n;for(let $=0;$<l.length;$++){const D=l.charAt($),H=i.get(D);if(H)i=H;else return[]}if(o)return i.get(ke)||[];const S=[],N=[i];for(;N.length;){const D=[...N.shift().entries()].sort((H,_)=>H[0]<_[0]?-1:1);for(const[H,_]of D)H===ke?S.push(..._):N.push(_)}return S}}const da=["name","url"];function la(t){const e=t&&Array.isArray(t),n=e&&t.length&&(!t[0]||da.some(a=>!(a in t[0])));if(!e||n)throw new Error("Custom emojis are in the wrong format")}function tt(t){la(t);const e=(_,R)=>_.name.toLowerCase()<R.name.toLowerCase()?-1:1,n=t.sort(e),l=ra(t,_=>{const R=new Set;if(_.shortcodes)for(const ae of _.shortcodes)for(const Z of ye(ae))R.add(Z);return R}),o=_=>l(_,!0),i=_=>l(_,!1),S=_=>{const R=ye(_),ae=R.map((Z,se)=>(se<R.length-1?o:i)(Z));return xt(ae,Z=>Z.name).sort(e)},N=new Map,$=new Map;for(const _ of t){$.set(_.name.toLowerCase(),_);for(const R of _.shortcodes||[])N.set(R.toLowerCase(),_)}return{all:n,search:S,byShortcode:_=>N.get(_.toLowerCase()),byName:_=>$.get(_.toLowerCase())}}const ca=typeof wrappedJSObject<"u";function xe(t){if(!t)return t;if(ca&&(t=structuredClone(t)),delete t.tokens,t.skinTones){const e=t.skinTones.length;t.skins=Array(e);for(let n=0;n<e;n++)t.skins[n]={tone:t.skinTones[n],unicode:t.skinUnicodes[n],version:t.skinVersions[n]};delete t.skinTones,delete t.skinUnicodes,delete t.skinVersions}return t}function kt(t){t||console.warn("emoji-picker-element is more efficient if the dataSource server exposes an ETag header.")}const pa=["annotation","emoji","group","order","version"];function ua(t){if(!t||!Array.isArray(t)||!t[0]||typeof t[0]!="object"||pa.some(e=>!(e in t[0])))throw new Error("Emoji data is in the wrong format")}function Et(t,e){if(Math.floor(t.status/100)!==2)throw new Error("Failed to fetch: "+e+": "+t.status)}async function ga(t){const e=await fetch(t,{method:"HEAD"});Et(e,t);const n=e.headers.get("etag");return kt(n),n}async function Ue(t){const e=await fetch(t);Et(e,t);const n=e.headers.get("etag");kt(n);const a=await e.json();return ua(a),[n,a]}function ma(t){for(var e="",n=new Uint8Array(t),a=n.byteLength,l=-1;++l<a;)e+=String.fromCharCode(n[l]);return e}function ya(t){for(var e=t.length,n=new ArrayBuffer(e),a=new Uint8Array(n),l=-1;++l<e;)a[l]=t.charCodeAt(l);return n}async function Lt(t){const e=JSON.stringify(t);let n=ya(e);const a=await crypto.subtle.digest("SHA-1",n),l=ma(a);return btoa(l)}async function ha(t,e){let n,a=await ga(e);if(!a){const l=await Ue(e);a=l[0],n=l[1],a||(a=await Lt(n))}await Qt(t,e,a)||(n||(n=(await Ue(e))[1]),await ft(t,n,e,a))}async function ba(t,e){let[n,a]=await Ue(e);n||(n=await Lt(a)),await ft(t,a,e,n)}async function va(t,e){try{await ha(t,e)}catch(n){if(n.name!=="InvalidStateError")throw n}}class xa{constructor({dataSource:e=Ht,locale:n=Nt,customEmoji:a=[]}={}){this.dataSource=e,this.locale=n,this._dbName=`emoji-picker-element-${this.locale}`,this._db=void 0,this._lazyUpdate=void 0,this._custom=tt(a),this._clear=this._clear.bind(this),this._ready=this._init()}async _init(){const e=this._db=await Gt(this._dbName);Vt(this._dbName,this._clear);const n=this.dataSource;await Zt(e)?await ba(e,n):this._lazyUpdate=va(e,n)}async ready(){const e=async()=>(this._ready||(this._ready=this._init()),this._ready);await e(),this._db||await e()}async getEmojiByGroup(e){return ze(e),await this.ready(),et(await ta(this._db,e)).map(xe)}async getEmojiBySearchQuery(e){we(e),await this.ready();const n=this._custom.search(e),a=et(await wt(this._db,e)).map(xe);return[...n,...a]}async getEmojiByShortcode(e){we(e),await this.ready();const n=this._custom.byShortcode(e);return n||xe(await aa(this._db,e))}async getEmojiByUnicodeOrName(e){we(e),await this.ready();const n=this._custom.byName(e);return n||xe(await na(this._db,e))}async getPreferredSkinTone(){return await this.ready(),await Ye(this._db,he,Qe)||0}async setPreferredSkinTone(e){return ze(e),await this.ready(),sa(this._db,he,Qe,e)}async incrementFavoriteEmojiCount(e){return we(e),await this.ready(),oa(this._db,e)}async getTopFavoriteEmoji(e){return ze(e),await this.ready(),(await ia(this._db,this._custom,e)).map(xe)}set customEmoji(e){this._custom=tt(e)}get customEmoji(){return this._custom.all}async _shutdown(){await this.ready();try{await this._lazyUpdate}catch{}}_clear(){this._db=this._ready=this._lazyUpdate=void 0}async close(){await this._shutdown(),await Ve(this._dbName)}async delete(){await this._shutdown(),await Wt(this._dbName)}}const Re=[[-1,"✨","custom"],[0,"😀","smileys-emotion"],[1,"👋","people-body"],[3,"🐱","animals-nature"],[4,"🍎","food-drink"],[5,"🏠️","travel-places"],[6,"⚽","activities"],[7,"📝","objects"],[8,"⛔️","symbols"],[9,"🏁","flags"]].map(([t,e,n])=>({id:t,emoji:e,name:n})),je=Re.slice(1),fa=2,at=6,$t=typeof requestIdleCallback=="function"?requestIdleCallback:setTimeout;function nt(t){return t.unicode.includes("‍")}const wa={"🫪":17,"🫩":16,"🫨":15.1,"🫠":14,"🥲":13.1,"🥻":12.1,"🥰":11,"🤩":5,"👱‍♀️":4,"🤣":3,"👁️‍🗨️":2,"😀":1,"😐️":.7,"😃":.6},ka=1e3,Ea="🖐️",La=8,$a=["😊","😒","❤️","👍️","😍","😂","😭","☺️","😔","😩","😏","💕","🙌","😘"],Tt='"Twemoji Mozilla","Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji","EmojiOne Color","Android Emoji",sans-serif',Ta=(t,e)=>t<e?-1:t>e?1:0,st=(t,e)=>{const n=document.createElement("canvas");n.width=n.height=1;const a=n.getContext("2d",{willReadFrequently:!0});return a.textBaseline="top",a.font=`100px ${Tt}`,a.fillStyle=e,a.scale(.01,.01),a.fillText(t,0,0),a.getImageData(0,0,1,1).data},Ia=(t,e)=>{const n=[...t].join(","),a=[...e].join(",");return n===a&&!n.startsWith("0,0,0,")};function Sa(t){const e=st(t,"#000"),n=st(t,"#fff");return e&&n&&Ia(e,n)}function Ba(){const t=Object.entries(wa);try{for(const[e,n]of t)if(Sa(e))return n}catch{}finally{}return t[0][1]}let De;const _e=()=>(De||(De=new Promise(t=>$t(()=>t(Ba())))),De),qe=new Map,Ma="️",Ca="\uD83C",za="‍",ja=127995,Da=57339;function _a(t,e){if(e===0)return t;const n=t.indexOf(za);return n!==-1?t.substring(0,n)+String.fromCodePoint(ja+e-1)+t.substring(n):(t.endsWith(Ma)&&(t=t.substring(0,t.length-1)),t+Ca+String.fromCodePoint(Da+e-1))}function ce(t){t.preventDefault(),t.stopPropagation()}function Ae(t,e,n){return e+=t?-1:1,e<0?e=n.length-1:e>=n.length&&(e=0),e}function It(t,e){const n=new Set,a=[];for(const l of t){const o=e(l);n.has(o)||(n.add(o),a.push(l))}return a}function Aa(t,e){const n=a=>{const l={};for(const o of a)typeof o.tone=="number"&&o.version<=e&&(l[o.tone]=o.unicode);return l};return t.map(({unicode:a,skins:l,shortcodes:o,url:i,name:S,category:N,annotation:$})=>({unicode:a,name:S,shortcodes:o,url:i,category:N,annotation:$,id:a||S,skins:l&&n(l)}))}const $e=requestAnimationFrame;let Pa=typeof ResizeObserver=="function";function Fa(t,e,n){let a;Pa?(a=new ResizeObserver(n),a.observe(t)):$e(n),e.addEventListener("abort",()=>{a&&a.disconnect()})}function ot(t){{const e=document.createRange();return e.selectNode(t.firstChild),e.getBoundingClientRect().width}}let Pe;function Oa(t,e,n){let a=!0;for(const l of t){const o=n(l);if(!o)continue;const i=ot(o);typeof Pe>"u"&&(Pe=ot(e));const S=i/1.8<Pe;qe.set(l.unicode,S),S||(a=!1)}return a}function Ha(t){return It(t,e=>e)}function Na(t){t&&(t.scrollTop=0)}function fe(t,e,n){let a=t.get(e);return a||(a=n(),t.set(e,a)),a}function it(t){return""+t}function Ua(t){const e=document.createElement("template");return e.innerHTML=t,e}const Ra=new WeakMap,qa=new WeakMap,Ga=Symbol("un-keyed"),Wa="replaceChildren"in Element.prototype;function Va(t,e){Wa?t.replaceChildren(...e):(t.innerHTML="",t.append(...e))}function Ka(t,e){let n=t.firstChild,a=0;for(;n;){if(e[a]!==n)return!0;n=n.nextSibling,a++}return a!==e.length}function Ya(t,e){const{targetNode:n}=e;let{targetParentNode:a}=e,l=!1;a?l=Ka(a,t):(l=!0,e.targetNode=void 0,e.targetParentNode=a=n.parentNode),l&&Va(a,t)}function Ja(t,e){for(const n of e){const{targetNode:a,currentExpression:l,binding:{expressionIndex:o,attributeName:i,attributeValuePre:S,attributeValuePost:N}}=n,$=t[o];if(l!==$)if(n.currentExpression=$,i)if($===null)a.removeAttribute(i);else{const D=S+it($)+N;a.setAttribute(i,D)}else{let D;Array.isArray($)?Ya($,n):$ instanceof Element?(D=$,a.replaceWith(D)):a.nodeValue=it($),D&&(n.targetNode=D)}}}function Xa(t){let e="",n=!1,a=!1,l=-1;const o=new Map,i=[];let S=0;for(let $=0,D=t.length;$<D;$++){const H=t[$];if(e+=H.slice(S),$===D-1)break;for(let U=0;U<H.length;U++)switch(H.charAt(U)){case"<":{H.charAt(U+1)==="/"?i.pop():(n=!0,i.push(++l));break}case">":{n=!1,a=!1;break}case"=":{a=!0;break}}const _=i[i.length-1],R=fe(o,_,()=>[]);let ae,Z,se;if(a){const U=/(\S+)="?([^"=]*)$/.exec(H);ae=U[1],Z=U[2];const V=/^([^">]*)("?)/.exec(t[$+1]);se=V[1],e=e.slice(0,-1*U[0].length),S=V[0].length}else S=0;const le={attributeName:ae,attributeValuePre:Z,attributeValuePost:se,expressionIndex:$};R.push(le),!n&&!a&&(e+=" ")}return{template:Ua(e),elementsToBindings:o}}function rt(t,e,n){for(let a=0;a<t.length;a++){const l=t[a],o=l.attributeName?e:e.firstChild,i={binding:l,targetNode:o,targetParentNode:void 0,currentExpression:void 0};n.push(i)}}function Za(t,e){const n=[];let a;if(e.size===1&&(a=e.get(0)))rt(a,t,n);else{const l=document.createTreeWalker(t,NodeFilter.SHOW_ELEMENT);let o=t,i=-1;do{const S=e.get(++i);S&&rt(S,o,n)}while(o=l.nextNode())}return n}function Qa(t){const{template:e,elementsToBindings:n}=fe(Ra,t,()=>Xa(t)),a=e.cloneNode(!0).content.firstElementChild,l=Za(a,n);return function(i){return Ja(i,l),a}}function en(t){const e=fe(qa,t,()=>new Map);let n=Ga;function a(o,...i){const S=fe(e,o,()=>new Map);return fe(S,n,()=>Qa(o))(i)}function l(o,i,S){return o.map((N,$)=>{const D=n;n=S(N);try{return i(N,$)}finally{n=D}})}return{map:l,html:a}}function tn(t,e,n,a,l,o,i,S,N){const{labelWithSkin:$,titleForEmoji:D,unicodeWithSkin:H}=n,{html:_,map:R}=en(e);function ae(U,V,Q){return R(U,(ne,re)=>_`<button role="${V?"option":"menuitem"}" aria-selected="${V?re===e.activeSearchItem:null}" aria-label="${$(ne,e.currentSkinTone)}" title="${D(ne)}" class="${"emoji"+(V&&re===e.activeSearchItem?" active":"")+(ne.unicode?"":" custom-emoji")}" id="${`${Q}-${ne.id}`}" style="${ne.unicode?null:`--custom-emoji-background: url(${JSON.stringify(ne.url)})`}">${ne.unicode?H(ne,e.currentSkinTone):""}</button>`,ne=>`${Q}-${ne.id}`)}const se=_`<section data-ref="rootElement" class="picker" aria-label="${e.i18n.regionLabel}" style="${e.pickerStyle||""}"><div class="pad-top"></div><div class="search-row"><div class="search-wrapper"><input id="search" class="search" type="search" role="combobox" enterkeyhint="search" placeholder="${e.i18n.searchLabel}" autocapitalize="none" autocomplete="off" spellcheck="true" aria-expanded="${!!(e.searchMode&&e.currentEmojis.length)}" aria-controls="search-results" aria-describedby="search-description" aria-autocomplete="list" aria-activedescendant="${e.activeSearchItemId?`emo-${e.activeSearchItemId}`:null}" data-ref="searchElement" data-on-input="onSearchInput" data-on-keydown="onSearchKeydown"><label class="sr-only" for="search">${e.i18n.searchLabel}</label> <span id="search-description" class="sr-only">${e.i18n.searchDescription}</span></div><div class="skintone-button-wrapper ${e.skinTonePickerExpandedAfterAnimation?"expanded":""}"><button id="skintone-button" class="emoji ${e.skinTonePickerExpanded?"hide-focus":""}" aria-label="${e.skinToneButtonLabel}" title="${e.skinToneButtonLabel}" aria-describedby="skintone-description" aria-haspopup="listbox" aria-expanded="${e.skinTonePickerExpanded}" aria-controls="skintone-list" data-on-click="onClickSkinToneButton">${e.skinToneButtonText||""}</button></div><span id="skintone-description" class="sr-only">${e.i18n.skinToneDescription}</span><div data-ref="skinToneDropdown" id="skintone-list" class="skintone-list hide-focus ${e.skinTonePickerExpanded?"":"hidden no-animate"}" style="transform:translateY(${e.skinTonePickerExpanded?0:"calc(-1 * var(--num-skintones) * var(--total-emoji-size))"})" role="listbox" aria-label="${e.i18n.skinTonesLabel}" aria-activedescendant="skintone-${e.activeSkinTone}" aria-hidden="${!e.skinTonePickerExpanded}" tabIndex="-1" data-on-focusout="onSkinToneOptionsFocusOut" data-on-click="onSkinToneOptionsClick" data-on-keydown="onSkinToneOptionsKeydown" data-on-keyup="onSkinToneOptionsKeyup">${R(e.skinTones,(U,V)=>_`<div id="skintone-${V}" class="emoji ${V===e.activeSkinTone?"active":""}" aria-selected="${V===e.activeSkinTone}" role="option" title="${e.i18n.skinTones[V]}" aria-label="${e.i18n.skinTones[V]}">${U}</div>`,U=>U)}</div></div><div class="nav" role="tablist" style="grid-template-columns:repeat(${e.groups.length},1fr)" aria-label="${e.i18n.categoriesLabel}" data-on-keydown="onNavKeydown" data-on-click="onNavClick">${R(e.groups,U=>_`<button role="tab" class="nav-button" aria-controls="tab-${U.id}" aria-label="${e.i18n.categories[U.name]}" aria-selected="${!e.searchMode&&e.currentGroup.id===U.id}" title="${e.i18n.categories[U.name]}" data-group-id="${U.id}"><div class="nav-emoji emoji">${U.emoji}</div></button>`,U=>U.id)}</div><div class="indicator-wrapper"><div class="indicator" style="transform:translateX(${(e.isRtl?-1:1)*e.currentGroupIndex*100}%)"></div></div><div class="message ${e.message?"":"gone"}" role="alert" aria-live="polite">${e.message||""}</div><div data-ref="tabpanelElement" class="tabpanel ${!e.databaseLoaded||e.message?"gone":""}" role="${e.searchMode?"region":"tabpanel"}" aria-label="${e.searchMode?e.i18n.searchResultsLabel:e.i18n.categories[e.currentGroup.name]}" id="${e.searchMode?null:`tab-${e.currentGroup.id}`}" tabIndex="0" data-on-click="onEmojiClick"><div data-action="calculateEmojiGridStyle">${R(e.currentEmojisWithCategories,(U,V)=>_`<div><div id="menu-label-${V}" class="category ${e.currentEmojisWithCategories.length===1&&e.currentEmojisWithCategories[0].category===""?"gone":""}" aria-hidden="true">${e.searchMode?e.i18n.searchResultsLabel:U.category?U.category:e.currentEmojisWithCategories.length>1?e.i18n.categories.custom:e.i18n.categories[e.currentGroup.name]}</div><div class="emoji-menu ${V!==0&&!e.searchMode&&e.currentGroup.id===-1?"visibility-auto":""}" style="${`--num-rows: ${Math.ceil(U.emojis.length/e.numColumns)}`}" data-action="updateOnIntersection" role="${e.searchMode?"listbox":"menu"}" aria-labelledby="menu-label-${V}" id="${e.searchMode?"search-results":null}">${ae(U.emojis,e.searchMode,"emo")}</div></div>`,U=>U.category)}</div></div><div class="favorites onscreen emoji-menu ${e.message?"gone":""}" role="menu" aria-label="${e.i18n.favoritesLabel}" data-on-click="onEmojiClick">${ae(e.currentFavorites,!1,"fav")}</div><button data-ref="baselineEmoji" aria-hidden="true" tabindex="-1" class="abs-pos hidden emoji baseline-emoji">😀</button></section>`,le=(U,V)=>{for(const Q of t.querySelectorAll(`[${U}]`))V(Q,Q.getAttribute(U))};if(N){t.appendChild(se);for(const U of["click","focusout","input","keydown","keyup"])le(`data-on-${U}`,(V,Q)=>{V.addEventListener(U,a[Q])});le("data-ref",(U,V)=>{o[V]=U}),i.addEventListener("abort",()=>{t.removeChild(se)})}le("data-action",(U,V)=>{let Q=S.get(V);Q||S.set(V,Q=new WeakSet),Q.has(U)||(Q.add(U),l[V](U))})}const Se=typeof queueMicrotask=="function"?queueMicrotask:t=>Promise.resolve().then(t);function an(t){let e=!1,n;const a=new Map,l=new Set;let o;const i=()=>{if(e)return;const $=[...l];l.clear();try{for(const D of $)D()}finally{o=!1,l.size&&(o=!0,Se(i))}},S=new Proxy({},{get($,D){if(n){let H=a.get(D);H||(H=new Set,a.set(D,H)),H.add(n)}return $[D]},set($,D,H){if($[D]!==H){$[D]=H;const _=a.get(D);if(_){for(const R of _)l.add(R);o||(o=!0,Se(i))}}return!0}}),N=$=>{const D=()=>{const H=n;n=D;try{return $()}finally{n=H}};return D()};return t.addEventListener("abort",()=>{e=!0}),{state:S,createEffect:N}}function Fe(t,e,n){if(t.length!==e.length)return!1;for(let a=0;a<t.length;a++)if(!n(t[a],e[a]))return!1;return!0}const dt=new WeakMap;function nn(t,e,n){{const a=t.closest(".tabpanel");let l=dt.get(a);l||(l=new IntersectionObserver(n,{root:a,rootMargin:"50% 0px 50% 0px",threshold:0}),dt.set(a,l),e.addEventListener("abort",()=>{l.disconnect()})),l.observe(t)}}const Oe=[],{assign:Ee}=Object;function sn(t,e){const n={},a=new AbortController,l=a.signal,{state:o,createEffect:i}=an(l),S=new Map;Ee(o,{skinToneEmoji:void 0,i18n:void 0,database:void 0,customEmoji:void 0,customCategorySorting:void 0,emojiVersion:void 0}),Ee(o,e),Ee(o,{initialLoad:!0,currentEmojis:[],currentEmojisWithCategories:[],rawSearchText:"",searchText:"",searchMode:!1,activeSearchItem:-1,message:void 0,skinTonePickerExpanded:!1,skinTonePickerExpandedAfterAnimation:!1,currentSkinTone:0,activeSkinTone:0,skinToneButtonText:void 0,pickerStyle:void 0,skinToneButtonLabel:"",skinTones:[],currentFavorites:[],defaultFavoriteEmojis:void 0,numColumns:La,isRtl:!1,currentGroupIndex:0,groups:je,databaseLoaded:!1,activeSearchItemId:void 0}),i(()=>{o.currentGroup!==o.groups[o.currentGroupIndex]&&(o.currentGroup=o.groups[o.currentGroupIndex])});const N=s=>{t.getElementById(s).focus()},$=s=>t.getElementById(`emo-${s.id}`),D=(s,c)=>{n.rootElement.dispatchEvent(new CustomEvent(s,{detail:c,bubbles:!0,composed:!0}))},H=(s,c)=>s.id===c.id,_=(s,c)=>{const{category:v,emojis:w}=s,{category:P,emojis:p}=c;return v!==P?!1:Fe(w,p,H)},R=s=>{Fe(o.currentEmojis,s,H)||(o.currentEmojis=s)},ae=s=>{o.searchMode!==s&&(o.searchMode=s)},Z=s=>{Fe(o.currentEmojisWithCategories,s,_)||(o.currentEmojisWithCategories=s)},se=(s,c)=>c&&s.skins&&s.skins[c]||s.unicode,V={labelWithSkin:(s,c)=>Ha([s.name||se(s,c),s.annotation,...s.shortcodes||Oe].filter(Boolean)).join(", "),titleForEmoji:s=>s.annotation||(s.shortcodes||Oe).join(", "),unicodeWithSkin:se},Q={onClickSkinToneButton:B,onEmojiClick:u,onNavClick:oe,onNavKeydown:K,onSearchKeydown:O,onSkinToneOptionsClick:k,onSkinToneOptionsFocusOut:r,onSkinToneOptionsKeydown:y,onSkinToneOptionsKeyup:g,onSearchInput:d},ne={calculateEmojiGridStyle:E,updateOnIntersection:F};let re=!0;i(()=>{tn(t,o,V,Q,ne,n,l,S,re),re=!1}),o.emojiVersion||_e().then(s=>{s||(o.message=o.i18n.emojiUnsupportedMessage)}),i(()=>{async function s(){let c=!1;const v=setTimeout(()=>{c=!0,o.message=o.i18n.loadingMessage},ka);try{await o.database.ready(),o.databaseLoaded=!0}catch(w){console.error(w),o.message=o.i18n.networkErrorMessage}finally{clearTimeout(v),c&&(c=!1,o.message="")}}o.database&&s()}),i(()=>{o.pickerStyle=`
52
+ --num-groups: ${o.groups.length};
53
+ --indicator-opacity: ${o.searchMode?0:1};
54
+ --num-skintones: ${at};`}),i(()=>{o.customEmoji&&o.database&&T()}),i(()=>{o.customEmoji&&o.customEmoji.length?o.groups!==Re&&(o.groups=Re):o.groups!==je&&(o.currentGroupIndex&&o.currentGroupIndex--,o.groups=je)}),i(()=>{async function s(){o.databaseLoaded&&(o.currentSkinTone=await o.database.getPreferredSkinTone())}s()}),i(()=>{o.skinTones=Array(at).fill().map((s,c)=>_a(o.skinToneEmoji,c))}),i(()=>{o.skinToneButtonText=o.skinTones[o.currentSkinTone]}),i(()=>{o.skinToneButtonLabel=o.i18n.skinToneLabel.replace("{skinTone}",o.i18n.skinTones[o.currentSkinTone])}),i(()=>{async function s(){const{database:c}=o,v=(await Promise.all($a.map(w=>c.getEmojiByUnicodeOrName(w)))).filter(Boolean);o.defaultFavoriteEmojis=v}o.databaseLoaded&&s()});function T(){const{customEmoji:s,database:c}=o,v=s||Oe;c.customEmoji!==v&&(c.customEmoji=v)}i(()=>{async function s(){T();const{database:c,defaultFavoriteEmojis:v,numColumns:w}=o,P=await c.getTopFavoriteEmoji(w),p=await I(It([...P,...v],L=>L.unicode||L.name).slice(0,w));o.currentFavorites=p}o.databaseLoaded&&o.defaultFavoriteEmojis&&s()});function E(s){Fa(s,l,()=>{{const c=getComputedStyle(n.rootElement),v=parseInt(c.getPropertyValue("--num-columns"),10),w=c.getPropertyValue("direction")==="rtl";o.numColumns=v,o.isRtl=w}})}function F(s){nn(s,l,c=>{for(const{target:v,isIntersecting:w}of c)v.classList.toggle("onscreen",w)})}i(()=>{async function s(){const{searchText:c,currentGroup:v,databaseLoaded:w,customEmoji:P}=o;if(!w)o.currentEmojis=[],o.searchMode=!1;else if(c.length>=fa){const p=await W(c);o.searchText===c&&(R(p),ae(!0))}else{const{id:p}=v;if(p!==-1||P&&P.length){const L=await z(p);o.currentGroup.id===p&&(R(L),ae(!1))}}}s()});const x=()=>{$e(()=>Na(n.tabpanelElement))};i(()=>{const{currentEmojis:s,emojiVersion:c}=o,v=s.filter(w=>w.unicode).filter(w=>nt(w)&&!qe.has(w.unicode));if(!c&&v.length)R(s),$e(()=>f(v));else{const w=c?s:s.filter(M);R(w),x()}});function f(s){Oa(s,n.baselineEmoji,$)?x():o.currentEmojis=[...o.currentEmojis]}function M(s){return!s.unicode||!nt(s)||qe.get(s.unicode)}async function A(s){const c=o.emojiVersion||await _e();return s.filter(({version:v})=>!v||v<=c)}async function I(s){return Aa(s,o.emojiVersion||await _e())}async function z(s){const c=s===-1?o.customEmoji:await o.database.getEmojiByGroup(s);return I(await A(c))}async function W(s){return I(await A(await o.database.getEmojiBySearchQuery(s)))}i(()=>{}),i(()=>{function s(){const{searchMode:v,currentEmojis:w}=o;if(v)return[{category:"",emojis:w}];const P=new Map;for(const p of w){const L=p.category||"";let j=P.get(L);j||(j=[],P.set(L,j)),j.push(p)}return[...P.entries()].map(([p,L])=>({category:p,emojis:L})).sort((p,L)=>o.customCategorySorting(p.category,L.category))}const c=s();Z(c)}),i(()=>{o.activeSearchItemId=o.activeSearchItem!==-1&&o.currentEmojis[o.activeSearchItem].id}),i(()=>{const{rawSearchText:s}=o;$t(()=>{o.searchText=(s||"").trim(),o.activeSearchItem=-1})});function O(s){if(!o.searchMode||!o.currentEmojis.length)return;const c=v=>{ce(s),o.activeSearchItem=Ae(v,o.activeSearchItem,o.currentEmojis)};switch(s.key){case"ArrowDown":return c(!1);case"ArrowUp":return c(!0);case"Enter":if(o.activeSearchItem===-1)o.activeSearchItem=0;else return ce(s),m(o.currentEmojis[o.activeSearchItem].id)}}function oe(s){const{target:c}=s,v=c.closest(".nav-button");if(!v)return;const w=parseInt(v.dataset.groupId,10);n.searchElement.value="",o.rawSearchText="",o.searchText="",o.activeSearchItem=-1,o.currentGroupIndex=o.groups.findIndex(P=>P.id===w)}function K(s){const{target:c,key:v}=s,w=P=>{P&&(ce(s),P.focus())};switch(v){case"ArrowLeft":return w(c.previousElementSibling);case"ArrowRight":return w(c.nextElementSibling);case"Home":return w(c.parentElement.firstElementChild);case"End":return w(c.parentElement.lastElementChild)}}async function h(s){const c=await o.database.getEmojiByUnicodeOrName(s),v=[...o.currentEmojis,...o.currentFavorites].find(P=>P.id===s),w=v.unicode&&se(v,o.currentSkinTone);return await o.database.incrementFavoriteEmojiCount(s),{emoji:c,skinTone:o.currentSkinTone,...w&&{unicode:w},...v.name&&{name:v.name}}}async function m(s){const c=h(s);D("emoji-click-sync",c),D("emoji-click",await c)}function u(s){const{target:c}=s;if(!c.classList.contains("emoji"))return;ce(s);const v=c.id.substring(4);m(v)}function b(s){o.currentSkinTone=s,o.skinTonePickerExpanded=!1,N("skintone-button"),D("skin-tone-change",{skinTone:s}),o.database.setPreferredSkinTone(s)}function k(s){const{target:{id:c}}=s,v=c&&c.match(/^skintone-(\d)/);if(!v)return;ce(s);const w=parseInt(v[1],10);b(w)}function B(s){o.skinTonePickerExpanded=!o.skinTonePickerExpanded,o.activeSkinTone=o.currentSkinTone,o.skinTonePickerExpanded&&(ce(s),$e(()=>N("skintone-list")))}i(()=>{o.skinTonePickerExpanded?n.skinToneDropdown.addEventListener("transitionend",()=>{o.skinTonePickerExpandedAfterAnimation=!0},{once:!0}):o.skinTonePickerExpandedAfterAnimation=!1});function y(s){if(!o.skinTonePickerExpanded)return;const c=async v=>{ce(s),o.activeSkinTone=v};switch(s.key){case"ArrowUp":return c(Ae(!0,o.activeSkinTone,o.skinTones));case"ArrowDown":return c(Ae(!1,o.activeSkinTone,o.skinTones));case"Home":return c(0);case"End":return c(o.skinTones.length-1);case"Enter":return ce(s),b(o.activeSkinTone);case"Escape":return ce(s),o.skinTonePickerExpanded=!1,N("skintone-button")}}function g(s){if(o.skinTonePickerExpanded)switch(s.key){case" ":return ce(s),b(o.activeSkinTone)}}async function r(s){const{relatedTarget:c}=s;(!c||c.id!=="skintone-list")&&(o.skinTonePickerExpanded=!1)}function d(s){o.rawSearchText=s.target.value}return{$set(s){Ee(o,s)},$destroy(){a.abort()}}}const on="https://cdn.jsdelivr.net/npm/emoji-picker-element-data@^1/en/emojibase/data.json",rn="en";var dn={categoriesLabel:"Categories",emojiUnsupportedMessage:"Your browser does not support color emoji.",favoritesLabel:"Favorites",loadingMessage:"Loading…",networkErrorMessage:"Could not load emoji.",regionLabel:"Emoji picker",searchDescription:"When search results are available, press up or down to select and enter to choose.",searchLabel:"Search",searchResultsLabel:"Search results",skinToneDescription:"When expanded, press up or down to select and enter to choose.",skinToneLabel:"Choose a skin tone (currently {skinTone})",skinTonesLabel:"Skin tones",skinTones:["Default","Light","Medium-Light","Medium","Medium-Dark","Dark"],categories:{custom:"Custom","smileys-emotion":"Smileys and emoticons","people-body":"People and body","animals-nature":"Animals and nature","food-drink":"Food and drink","travel-places":"Travel and places",activities:"Activities",objects:"Objects",symbols:"Symbols",flags:"Flags"}},ln=':host{--emoji-size:1.375rem;--emoji-padding:0.5rem;--category-emoji-size:var(--emoji-size);--category-emoji-padding:var(--emoji-padding);--indicator-height:3px;--input-border-radius:0.5rem;--input-border-size:1px;--input-font-size:1rem;--input-line-height:1.5;--input-padding:0.25rem;--num-columns:8;--outline-size:2px;--border-size:1px;--border-radius:0;--skintone-border-radius:1rem;--category-font-size:1rem;display:flex;width:min-content;height:400px}:host,:host(.light){color-scheme:light;--background:#fff;--border-color:#e0e0e0;--indicator-color:#385ac1;--input-border-color:#999;--input-font-color:#111;--input-placeholder-color:#999;--outline-color:#999;--category-font-color:#111;--button-active-background:#e6e6e6;--button-hover-background:#d9d9d9}:host(.dark){color-scheme:dark;--background:#222;--border-color:#444;--indicator-color:#5373ec;--input-border-color:#ccc;--input-font-color:#efefef;--input-placeholder-color:#ccc;--outline-color:#fff;--category-font-color:#efefef;--button-active-background:#555555;--button-hover-background:#484848}@media (prefers-color-scheme:dark){:host{color-scheme:dark;--background:#222;--border-color:#444;--indicator-color:#5373ec;--input-border-color:#ccc;--input-font-color:#efefef;--input-placeholder-color:#ccc;--outline-color:#fff;--category-font-color:#efefef;--button-active-background:#555555;--button-hover-background:#484848}}:host([hidden]){display:none}button{margin:0;padding:0;border:0;background:0 0;box-shadow:none;-webkit-tap-highlight-color:transparent}button::-moz-focus-inner{border:0}input{padding:0;margin:0;line-height:1.15;font-family:inherit}input[type=search]{-webkit-appearance:none}:focus{outline:var(--outline-color) solid var(--outline-size);outline-offset:calc(-1*var(--outline-size))}:host([data-js-focus-visible]) :focus:not([data-focus-visible-added]){outline:0}:focus:not(:focus-visible){outline:0}.hide-focus{outline:0}*{box-sizing:border-box}.picker{contain:content;display:flex;flex-direction:column;background:var(--background);border:var(--border-size) solid var(--border-color);border-radius:var(--border-radius);width:100%;height:100%;overflow:hidden;--total-emoji-size:calc(var(--emoji-size) + (2 * var(--emoji-padding)));--total-category-emoji-size:calc(var(--category-emoji-size) + (2 * var(--category-emoji-padding)))}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.hidden{opacity:0;pointer-events:none}.abs-pos{position:absolute;left:0;top:0}.gone{display:none!important}.skintone-button-wrapper,.skintone-list{background:var(--background);z-index:3}.skintone-button-wrapper.expanded{z-index:1}.skintone-list{position:absolute;inset-inline-end:0;top:0;z-index:2;overflow:visible;border-bottom:var(--border-size) solid var(--border-color);border-radius:0 0 var(--skintone-border-radius) var(--skintone-border-radius);will-change:transform;transition:transform .2s ease-in-out;transform-origin:center 0}@media (prefers-reduced-motion:reduce){.skintone-list{transition-duration:.001s}}@supports not (inset-inline-end:0){.skintone-list{right:0}}.skintone-list.no-animate{transition:none}.tabpanel{overflow-y:auto;scrollbar-gutter:stable;-webkit-overflow-scrolling:touch;will-change:transform;min-height:0;flex:1;contain:content}.emoji-menu{display:grid;grid-template-columns:repeat(var(--num-columns),var(--total-emoji-size));justify-content:space-around;align-items:flex-start;width:100%}.emoji-menu.visibility-auto{content-visibility:auto;contain-intrinsic-size:calc(var(--num-columns)*var(--total-emoji-size)) calc(var(--num-rows)*var(--total-emoji-size))}.category{padding:var(--emoji-padding);font-size:var(--category-font-size);color:var(--category-font-color)}.emoji,button.emoji{font-size:var(--emoji-size);display:flex;align-items:center;justify-content:center;border-radius:100%;height:var(--total-emoji-size);width:var(--total-emoji-size);line-height:1;overflow:hidden;font-family:var(--emoji-font-family);cursor:pointer}@media (hover:hover) and (pointer:fine){.emoji:hover,button.emoji:hover{background:var(--button-hover-background)}}.emoji.active,.emoji:active,button.emoji.active,button.emoji:active{background:var(--button-active-background)}.onscreen .custom-emoji::after{content:"";width:var(--emoji-size);height:var(--emoji-size);background-repeat:no-repeat;background-position:center center;background-size:contain;background-image:var(--custom-emoji-background)}.nav,.nav-button{align-items:center}.nav{display:grid;justify-content:space-between;contain:content}.nav-button{display:flex;justify-content:center}.nav-emoji{font-size:var(--category-emoji-size);width:var(--total-category-emoji-size);height:var(--total-category-emoji-size)}.indicator-wrapper{display:flex;border-bottom:1px solid var(--border-color)}.indicator{width:calc(100%/var(--num-groups));height:var(--indicator-height);opacity:var(--indicator-opacity);background-color:var(--indicator-color);will-change:transform,opacity;transition:opacity .1s linear,transform .25s ease-in-out}@media (prefers-reduced-motion:reduce){.indicator{will-change:opacity;transition:opacity .1s linear}}.pad-top,input.search{background:var(--background);width:100%}.pad-top{height:var(--emoji-padding);z-index:3}.search-row{display:flex;align-items:center;position:relative;padding-inline-start:var(--emoji-padding);padding-bottom:var(--emoji-padding)}.search-wrapper{flex:1;min-width:0}input.search{padding:var(--input-padding);border-radius:var(--input-border-radius);border:var(--input-border-size) solid var(--input-border-color);color:var(--input-font-color);font-size:var(--input-font-size);line-height:var(--input-line-height)}input.search::placeholder{color:var(--input-placeholder-color)}.favorites{overflow-y:auto;scrollbar-gutter:stable;display:flex;flex-direction:row;border-top:var(--border-size) solid var(--border-color);contain:content}.message{padding:var(--emoji-padding)}';const St=["customEmoji","customCategorySorting","database","dataSource","i18n","locale","skinToneEmoji","emojiVersion"],cn=`:host{--emoji-font-family:${Tt}}`;class Je extends HTMLElement{constructor(e){super(),this.attachShadow({mode:"open"});const n=document.createElement("style");n.textContent=ln+cn,this.shadowRoot.appendChild(n),this._ctx={locale:rn,dataSource:on,skinToneEmoji:Ea,customCategorySorting:Ta,customEmoji:null,i18n:dn,emojiVersion:null,...e};for(const a of St)a!=="database"&&Object.prototype.hasOwnProperty.call(this,a)&&(this._ctx[a]=this[a],delete this[a]);this._dbFlush()}connectedCallback(){lt(this),this._cmp||(this._cmp=sn(this.shadowRoot,this._ctx))}disconnectedCallback(){lt(this),Se(()=>{if(!this.isConnected&&this._cmp){this._cmp.$destroy(),this._cmp=void 0;const{database:e}=this._ctx;e.close().catch(n=>console.error(n))}})}static get observedAttributes(){return["locale","data-source","skin-tone-emoji","emoji-version"]}attributeChangedCallback(e,n,a){this._set(e.replace(/-([a-z])/g,(l,o)=>o.toUpperCase()),e==="emoji-version"?parseFloat(a):a)}_set(e,n){this._ctx[e]=n,this._cmp&&this._cmp.$set({[e]:n}),["locale","dataSource"].includes(e)&&this._dbFlush()}_dbCreate(){const{locale:e,dataSource:n,database:a}=this._ctx;(!a||a.locale!==e||a.dataSource!==n)&&this._set("database",new xa({locale:e,dataSource:n}))}_dbFlush(){Se(()=>this._dbCreate())}}const Bt={};for(const t of St)Bt[t]={get(){return t==="database"&&this._dbCreate(),this._ctx[t]},set(e){if(t==="database")throw new Error("database is read-only");this._set(t,e)}};Object.defineProperties(Je.prototype,Bt);function lt(t){t instanceof Je||Object.setPrototypeOf(t,customElements.get(t.tagName.toLowerCase()).prototype)}customElements.get("emoji-picker")||customElements.define("emoji-picker",Je);function pn(t,e){const n=document.getElementById("app"),a=new ct,l=new Be,o=t.id||t._id;let i=null,S=[];const N=localStorage.getItem("currentTheme")||"dark";un(N),n.innerHTML=`
52
55
  <div class="dashboard">
53
56
  <aside class="sidebar">
54
57
  <div class="sidebar-header">
@@ -57,9 +60,9 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
57
60
  </div>
58
61
 
59
62
  <div class="user-info">
60
- <div class="avatar">${_.username[0].toUpperCase()}</div>
63
+ <div class="avatar">${t.username[0].toUpperCase()}</div>
61
64
  <div>
62
- <div class="username">${_.username}</div>
65
+ <div class="username">${t.username}</div>
63
66
  <div class="user-role">管理员</div>
64
67
  </div>
65
68
  </div>
@@ -120,7 +123,7 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
120
123
  <div id="contentArea"></div>
121
124
  </main>
122
125
  </div>
123
- `,document.querySelectorAll(".nav-item").forEach(o=>{o.addEventListener("click",()=>{document.querySelectorAll(".nav-item").forEach(n=>n.classList.remove("active")),o.classList.add("active");const i=o.dataset.view;F(i)})}),document.getElementById("logoutBtn").addEventListener("click",()=>{z.logout()}),document.getElementById("settingsBtn").addEventListener("click",()=>{F("settings")}),document.getElementById("helpBtn").addEventListener("click",()=>{F("help")});async function te(o){R=(await d.getGroups()).groups,o.innerHTML=`
126
+ `,document.querySelectorAll(".nav-item").forEach(h=>{h.addEventListener("click",()=>{document.querySelectorAll(".nav-item").forEach(u=>u.classList.remove("active")),h.classList.add("active");const m=h.dataset.view;K(m)})}),document.getElementById("logoutBtn").addEventListener("click",()=>{l.logout()}),document.getElementById("settingsBtn").addEventListener("click",()=>{K("settings")}),document.getElementById("helpBtn").addEventListener("click",()=>{K("help")});async function $(h){S=(await a.getGroups()).groups,h.innerHTML=`
124
127
  <div class="view-header">
125
128
  <h2>群组管理</h2>
126
129
  <button class="btn-primary" id="createGroupBtn">创建群组</button>
@@ -162,47 +165,47 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
162
165
  <button type="button" class="btn-secondary" id="closeMembersModal">关闭</button>
163
166
  </div>
164
167
  </div>
165
- `;const n=document.getElementById("groupsList");R.forEach(s=>{const x=document.createElement("div");x.className="group-card",x.innerHTML=`
166
- <h3>${s.name}</h3>
167
- <p>${s.description||"暂无描述"}</p>
168
+ `;const u=document.getElementById("groupsList");S.forEach(b=>{const k=document.createElement("div");k.className="group-card",k.innerHTML=`
169
+ <h3>${b.name}</h3>
170
+ <p>${b.description||"暂无描述"}</p>
168
171
  <div class="group-stats">
169
- <span>👥 ${s.members.length} 成员</span>
170
- <span>📄 ${s.documents.length} 文档</span>
172
+ <span>👥 ${b.members.length} 成员</span>
173
+ <span>📄 ${b.documents.length} 文档</span>
171
174
  </div>
172
175
  <div style="display: flex; gap: 10px; margin-top: 10px;">
173
- <button class="btn-select" data-id="${s._id}">选择</button>
174
- <button class="btn-secondary" data-id="${s._id}" data-action="manage">管理成员</button>
176
+ <button class="btn-select" data-id="${b._id}">选择</button>
177
+ <button class="btn-secondary" data-id="${b._id}" data-action="manage">管理成员</button>
175
178
  </div>
176
- `,n.appendChild(x)}),document.querySelectorAll(".btn-select").forEach(s=>{s.addEventListener("click",()=>{p=R.find(x=>x._id===s.dataset.id),a.joinGroup(p._id),alert(`已加入群组: ${p.name}`)})}),document.querySelectorAll('[data-action="manage"]').forEach(s=>{s.addEventListener("click",async()=>{const x=s.dataset.id;await Q(x)})}),document.getElementById("createGroupBtn").addEventListener("click",async()=>{document.getElementById("createGroupModal").classList.remove("hidden"),await ne()}),document.getElementById("closeModal").addEventListener("click",()=>{document.getElementById("createGroupModal").classList.add("hidden")}),document.getElementById("closeMembersModal").addEventListener("click",()=>{document.getElementById("manageMembersModal").classList.add("hidden")}),document.getElementById("createGroupForm").addEventListener("submit",async s=>{s.preventDefault();const x=new FormData(s.target),B=Array.from(document.querySelectorAll("#usersList input:checked")).map(r=>r.value);try{const r=await d.createGroup(x.get("name"),x.get("description"),B);alert("群组创建成功!"),await te(o),document.getElementById("createGroupModal").classList.add("hidden")}catch(r){console.error("创建群组错误:",r),alert("创建失败: "+r.message)}})}async function ne(){try{const o=await d.getAllUsers(),i=document.getElementById("usersList");i.innerHTML=o.users.map(n=>`
179
+ `,u.appendChild(k)}),document.querySelectorAll(".btn-select").forEach(b=>{b.addEventListener("click",()=>{i=S.find(k=>k._id===b.dataset.id),e.joinGroup(i._id),alert(`已加入群组: ${i.name}`)})}),document.querySelectorAll('[data-action="manage"]').forEach(b=>{b.addEventListener("click",async()=>{const k=b.dataset.id;await H(k)})}),document.getElementById("createGroupBtn").addEventListener("click",async()=>{document.getElementById("createGroupModal").classList.remove("hidden"),await D()}),document.getElementById("closeModal").addEventListener("click",()=>{document.getElementById("createGroupModal").classList.add("hidden")}),document.getElementById("closeMembersModal").addEventListener("click",()=>{document.getElementById("manageMembersModal").classList.add("hidden")}),document.getElementById("createGroupForm").addEventListener("submit",async b=>{b.preventDefault();const k=new FormData(b.target),B=Array.from(document.querySelectorAll("#usersList input:checked")).map(y=>y.value);try{const y=await a.createGroup(k.get("name"),k.get("description"),B);alert("群组创建成功!"),await $(h),document.getElementById("createGroupModal").classList.add("hidden")}catch(y){console.error("创建群组错误:",y),alert("创建失败: "+y.message)}})}async function D(){try{const h=await a.getAllUsers(),m=document.getElementById("usersList");m.innerHTML=h.users.map(u=>`
177
180
  <label style="display: flex; align-items: center; gap: 10px; padding: 8px; cursor: pointer;">
178
- <input type="checkbox" value="${n._id}">
179
- <div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${n.username[0].toUpperCase()}</div>
180
- <span>${n.username} (${n.role==="admin"?"管理员":"用户"})</span>
181
+ <input type="checkbox" value="${u._id}">
182
+ <div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${u.username[0].toUpperCase()}</div>
183
+ <span>${u.username} (${u.role==="admin"?"管理员":"用户"})</span>
181
184
  </label>
182
- `).join("")}catch(o){console.error("加载用户失败:",o),document.getElementById("usersList").innerHTML='<p style="color: var(--danger);">加载失败</p>'}}async function Q(o){try{const i=await d.getGroup(o),n=await d.getAllUsers(),s=i.group,x=document.getElementById("currentMembers");x.innerHTML=`
183
- <h4>当前成员 (${s.members.length})</h4>
185
+ `).join("")}catch(h){console.error("加载用户失败:",h),document.getElementById("usersList").innerHTML='<p style="color: var(--danger);">加载失败</p>'}}async function H(h){try{const m=await a.getGroup(h),u=await a.getAllUsers(),b=m.group,k=document.getElementById("currentMembers");k.innerHTML=`
186
+ <h4>当前成员 (${b.members.length})</h4>
184
187
  <div style="max-height: 200px; overflow-y: auto;">
185
- ${s.members.map(e=>`
188
+ ${b.members.map(r=>`
186
189
  <div style="display: flex; align-items: center; justify-content: space-between; padding: 8px; border-bottom: 1px solid var(--border);">
187
190
  <div style="display: flex; align-items: center; gap: 10px;">
188
- <div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${e.username[0].toUpperCase()}</div>
189
- <span>${e.username} ${e._id.toString()===s.admin._id.toString()?"(管理员)":""}</span>
191
+ <div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${r.username[0].toUpperCase()}</div>
192
+ <span>${r.username} ${r._id.toString()===b.admin._id.toString()?"(管理员)":""}</span>
190
193
  </div>
191
- ${e._id.toString()!==s.admin._id.toString()?`<button class="btn-secondary btn-sm" onclick="removeMember('${o}', '${e._id}')">移除</button>`:""}
194
+ ${r._id.toString()!==b.admin._id.toString()?`<button class="btn-secondary btn-sm" onclick="removeMember('${h}', '${r._id}')">移除</button>`:""}
192
195
  </div>
193
196
  `).join("")}
194
197
  </div>
195
- `;const B=s.members.map(e=>e._id.toString()),r=n.users.filter(e=>!B.includes(e._id)),t=document.getElementById("availableUsers");r.length===0?t.innerHTML="<p>所有用户都已在群组中</p>":t.innerHTML=r.map(e=>`
198
+ `;const B=b.members.map(r=>r._id.toString()),y=u.users.filter(r=>!B.includes(r._id)),g=document.getElementById("availableUsers");y.length===0?g.innerHTML="<p>所有用户都已在群组中</p>":g.innerHTML=y.map(r=>`
196
199
  <div style="display: flex; align-items: center; justify-content: space-between; padding: 8px; border-bottom: 1px solid var(--border);">
197
200
  <div style="display: flex; align-items: center; gap: 10px;">
198
- <div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${e.username[0].toUpperCase()}</div>
199
- <span>${e.username}</span>
201
+ <div class="avatar" style="width: 30px; height: 30px; font-size: 14px;">${r.username[0].toUpperCase()}</div>
202
+ <span>${r.username}</span>
200
203
  </div>
201
- <button class="btn-primary btn-sm" onclick="addMember('${o}', '${e._id}')">添加</button>
204
+ <button class="btn-primary btn-sm" onclick="addMember('${h}', '${r._id}')">添加</button>
202
205
  </div>
203
- `).join(""),document.getElementById("manageMembersModal").classList.remove("hidden")}catch(i){console.error("加载成员失败:",i),alert("加载失败: "+i.message)}}window.addMember=async(o,i)=>{try{await d.addMember(o,i),alert("成员添加成功!"),await Q(o)}catch(n){alert("添加失败: "+n.message)}},window.removeMember=async(o,i)=>{if(confirm("确定要移除该成员吗?"))try{await d.removeMember(o,i),alert("成员移除成功!"),await Q(o)}catch(n){alert("移除失败: "+n.message)}};async function K(o){if(!p){o.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}const i=await d.getTasks(p._id);o.innerHTML=`
206
+ `).join(""),document.getElementById("manageMembersModal").classList.remove("hidden")}catch(m){console.error("加载成员失败:",m),alert("加载失败: "+m.message)}}window.addMember=async(h,m)=>{try{await a.addMember(h,m),alert("成员添加成功!"),await H(h)}catch(u){alert("添加失败: "+u.message)}},window.removeMember=async(h,m)=>{if(confirm("确定要移除该成员吗?"))try{await a.removeMember(h,m),alert("成员移除成功!"),await H(h)}catch(u){alert("移除失败: "+u.message)}};async function _(h){if(!i){h.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}const m=await a.getTasks(i._id);h.innerHTML=`
204
207
  <div class="view-header">
205
- <h2>任务管理 - ${p.name}</h2>
208
+ <h2>任务管理 - ${i.name}</h2>
206
209
  <button class="btn-primary" id="createTaskBtn">创建任务</button>
207
210
  </div>
208
211
  <div class="tasks-list" id="tasksList"></div>
@@ -242,30 +245,30 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
242
245
  </div>
243
246
  </div>
244
247
  </div>
245
- `;const n=document.getElementById("tasksList");i.tasks.length===0?n.innerHTML='<div class="empty-state">暂无任务</div>':(i.tasks.forEach(s=>{const x=document.createElement("div");x.className=`task-card status-${s.status}`,x.innerHTML=`
248
+ `;const u=document.getElementById("tasksList");m.tasks.length===0?u.innerHTML='<div class="empty-state">暂无任务</div>':(m.tasks.forEach(b=>{const k=document.createElement("div");k.className=`task-card status-${b.status}`,k.innerHTML=`
246
249
  <div style="display: flex; justify-content: space-between; align-items: start;">
247
250
  <div style="flex: 1;">
248
- <h3>${s.title}</h3>
249
- <p>${s.description||"无描述"}</p>
251
+ <h3>${b.title}</h3>
252
+ <p>${b.description||"无描述"}</p>
250
253
  <div class="task-meta">
251
- <span class="status-badge">${b(s.status)}</span>
252
- <span>截止: ${s.deadline?new Date(s.deadline).toLocaleDateString():"无"}</span>
254
+ <span class="status-badge">${E(b.status)}</span>
255
+ <span>截止: ${b.deadline?new Date(b.deadline).toLocaleDateString():"无"}</span>
253
256
  </div>
254
257
  </div>
255
258
  <div style="display: flex; gap: 10px;">
256
- <button class="btn-primary btn-sm" data-id="${s._id}" data-action="view-task" title="查看详细">📋 查看详细</button>
257
- <button class="btn-danger btn-sm" data-id="${s._id}" data-action="delete-task" title="删除任务" style="min-width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;">🗑️ 删除</button>
259
+ <button class="btn-primary btn-sm" data-id="${b._id}" data-action="view-task" title="查看详细">📋 查看详细</button>
260
+ <button class="btn-danger btn-sm" data-id="${b._id}" data-action="delete-task" title="删除任务" style="min-width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;">🗑️ 删除</button>
258
261
  </div>
259
- `,n.appendChild(x)}),document.querySelectorAll('[data-action="delete-task"]').forEach(s=>{s.addEventListener("click",async x=>{x.stopPropagation();const B=s.dataset.id;if(confirm("确定要删除这个任务吗?删除后无法恢复!"))try{await d.deleteTask(B),alert("任务删除成功!"),await K(o)}catch(r){console.error("删除任务错误:",r),alert("删除失败: "+(r.message||"未知错误"))}})}),document.querySelectorAll('[data-action="view-task"]').forEach(s=>{s.addEventListener("click",async x=>{var t;x.stopPropagation();const B=s.dataset.id,r=i.tasks.find(e=>e._id===B);if(r){const e=document.getElementById("taskDetailContent"),l=c=>({pending:"#6366f1","in-progress":"#f59e0b",completed:"#10b981",cancelled:"#ef4444"})[c]||"#6366f1";e.innerHTML=`
262
+ `,u.appendChild(k)}),document.querySelectorAll('[data-action="delete-task"]').forEach(b=>{b.addEventListener("click",async k=>{k.stopPropagation();const B=b.dataset.id;if(confirm("确定要删除这个任务吗?删除后无法恢复!"))try{await a.deleteTask(B),alert("任务删除成功!"),await _(h)}catch(y){console.error("删除任务错误:",y),alert("删除失败: "+(y.message||"未知错误"))}})}),document.querySelectorAll('[data-action="view-task"]').forEach(b=>{b.addEventListener("click",async k=>{var g;k.stopPropagation();const B=b.dataset.id,y=m.tasks.find(r=>r._id===B);if(y){const r=document.getElementById("taskDetailContent"),d=s=>({pending:"#6366f1","in-progress":"#f59e0b",completed:"#10b981",cancelled:"#ef4444"})[s]||"#6366f1";r.innerHTML=`
260
263
  <div class="task-detail" style="background: linear-gradient(135deg, rgba(99, 102, 241, 0.05) 0%, rgba(168, 85, 247, 0.05) 100%); border-radius: 12px; padding: 16px;">
261
264
 
262
265
  <!-- 任务标题 -->
263
- <div class="detail-section" style="background: var(--bg-secondary); border-radius: 8px; padding: 12px; margin-bottom: 12px; border-left: 4px solid ${l(r.status)};">
266
+ <div class="detail-section" style="background: var(--bg-secondary); border-radius: 8px; padding: 12px; margin-bottom: 12px; border-left: 4px solid ${d(y.status)};">
264
267
  <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 6px;">
265
268
  <span style="font-size: 18px;">📌</span>
266
269
  <h4 style="margin: 0; color: var(--text-secondary); font-size: 12px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;">任务标题</h4>
267
270
  </div>
268
- <p style="font-size: 18px; font-weight: 700; margin: 0; color: var(--text-primary); line-height: 1.3;">${r.title}</p>
271
+ <p style="font-size: 18px; font-weight: 700; margin: 0; color: var(--text-primary); line-height: 1.3;">${y.title}</p>
269
272
  </div>
270
273
 
271
274
  <!-- 任务描述 -->
@@ -274,7 +277,7 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
274
277
  <span style="font-size: 18px;">📝</span>
275
278
  <h4 style="margin: 0; color: var(--text-secondary); font-size: 12px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;">任务描述</h4>
276
279
  </div>
277
- <p style="margin: 0; line-height: 1.6; color: var(--text-primary); font-size: 14px; white-space: pre-wrap;">${r.description||'<span style="color: var(--text-secondary); font-style: italic;">暂无描述</span>'}</p>
280
+ <p style="margin: 0; line-height: 1.6; color: var(--text-primary); font-size: 14px; white-space: pre-wrap;">${y.description||'<span style="color: var(--text-secondary); font-style: italic;">暂无描述</span>'}</p>
278
281
  </div>
279
282
 
280
283
  <!-- 状态和日期网格 -->
@@ -286,9 +289,9 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
286
289
  <span style="font-size: 18px;">📊</span>
287
290
  <h4 style="margin: 0; color: var(--text-secondary); font-size: 12px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;">任务状态</h4>
288
291
  </div>
289
- <span style="display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 6px; background: ${l(r.status)}; color: white; font-weight: 600; font-size: 13px; box-shadow: 0 2px 6px rgba(0,0,0,0.15);">
290
- ${r.status==="completed"?"✓":r.status==="in-progress"?"⟳":r.status==="cancelled"?"✕":"○"}
291
- ${b(r.status)}
292
+ <span style="display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 6px; background: ${d(y.status)}; color: white; font-weight: 600; font-size: 13px; box-shadow: 0 2px 6px rgba(0,0,0,0.15);">
293
+ ${y.status==="completed"?"✓":y.status==="in-progress"?"⟳":y.status==="cancelled"?"✕":"○"}
294
+ ${E(y.status)}
292
295
  </span>
293
296
  </div>
294
297
 
@@ -299,7 +302,7 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
299
302
  <h4 style="margin: 0; color: var(--text-secondary); font-size: 12px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;">截止日期</h4>
300
303
  </div>
301
304
  <p style="margin: 0; font-size: 13px; font-weight: 600; color: var(--text-primary);">
302
- ${r.deadline?new Date(r.deadline).toLocaleString("zh-CN",{year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"}):'<span style="color: var(--text-secondary); font-style: italic;">无截止日期</span>'}
305
+ ${y.deadline?new Date(y.deadline).toLocaleString("zh-CN",{year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"}):'<span style="color: var(--text-secondary); font-style: italic;">无截止日期</span>'}
303
306
  </p>
304
307
  </div>
305
308
 
@@ -310,24 +313,24 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
310
313
  <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px;">
311
314
  <span style="font-size: 18px;">👥</span>
312
315
  <h4 style="margin: 0; color: var(--text-secondary); font-size: 12px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;">分配给</h4>
313
- ${r.assignedTo&&r.assignedTo.length>0?`<span style="background: var(--primary); color: white; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600;">${r.assignedTo.length} 人</span>`:""}
316
+ ${y.assignedTo&&y.assignedTo.length>0?`<span style="background: var(--primary); color: white; padding: 2px 8px; border-radius: 10px; font-size: 11px; font-weight: 600;">${y.assignedTo.length} 人</span>`:""}
314
317
  </div>
315
318
  <div style="display: flex; flex-wrap: wrap; gap: 8px;">
316
- ${r.assignedTo&&r.assignedTo.length>0?r.assignedTo.map(c=>{var k,A;const h=r.completedBy&&r.completedBy.some(g=>g.user&&g.user._id===c._id),E=h?r.completedBy.find(g=>g.user&&g.user._id===c._id):null;return`
317
- <div style="display: flex; align-items: center; gap: 8px; padding: 8px 12px; background: linear-gradient(135deg, ${h?"rgba(16, 185, 129, 0.1)":"rgba(99, 102, 241, 0.1)"} 0%, ${h?"rgba(5, 150, 105, 0.1)":"rgba(168, 85, 247, 0.1)"} 100%); border-radius: 8px; border: 1px solid ${h?"rgba(16, 185, 129, 0.3)":"rgba(99, 102, 241, 0.2)"}; transition: all 0.3s ease; position: relative;">
318
- <div class="avatar" style="width: 30px; height: 30px; font-size: 14px; background: linear-gradient(135deg, ${h?"#10b981 0%, #059669":"#6366f1 0%, #a855f7"} 100%); box-shadow: 0 2px 6px ${h?"rgba(16, 185, 129, 0.3)":"rgba(99, 102, 241, 0.3)"};">${((A=(k=c.username)==null?void 0:k[0])==null?void 0:A.toUpperCase())||"?"}</div>
319
+ ${y.assignedTo&&y.assignedTo.length>0?y.assignedTo.map(s=>{var w,P;const c=y.completedBy&&y.completedBy.some(p=>p.user&&p.user._id===s._id),v=c?y.completedBy.find(p=>p.user&&p.user._id===s._id):null;return`
320
+ <div style="display: flex; align-items: center; gap: 8px; padding: 8px 12px; background: linear-gradient(135deg, ${c?"rgba(16, 185, 129, 0.1)":"rgba(99, 102, 241, 0.1)"} 0%, ${c?"rgba(5, 150, 105, 0.1)":"rgba(168, 85, 247, 0.1)"} 100%); border-radius: 8px; border: 1px solid ${c?"rgba(16, 185, 129, 0.3)":"rgba(99, 102, 241, 0.2)"}; transition: all 0.3s ease; position: relative;">
321
+ <div class="avatar" style="width: 30px; height: 30px; font-size: 14px; background: linear-gradient(135deg, ${c?"#10b981 0%, #059669":"#6366f1 0%, #a855f7"} 100%); box-shadow: 0 2px 6px ${c?"rgba(16, 185, 129, 0.3)":"rgba(99, 102, 241, 0.3)"};">${((P=(w=s.username)==null?void 0:w[0])==null?void 0:P.toUpperCase())||"?"}</div>
319
322
  <div style="display: flex; flex-direction: column; gap: 1px;">
320
- <span style="font-weight: 600; font-size: 13px; color: var(--text-primary);">${c.username||"未知用户"}</span>
321
- ${h?`<span style="font-size: 10px; color: #10b981; font-weight: 500;">✓ ${E&&E.completedAt?new Date(E.completedAt).toLocaleString("zh-CN",{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"}):"已完成"}</span>`:'<span style="font-size: 10px; color: var(--text-tertiary);">未完成</span>'}
323
+ <span style="font-weight: 600; font-size: 13px; color: var(--text-primary);">${s.username||"未知用户"}</span>
324
+ ${c?`<span style="font-size: 10px; color: #10b981; font-weight: 500;">✓ ${v&&v.completedAt?new Date(v.completedAt).toLocaleString("zh-CN",{month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"}):"已完成"}</span>`:'<span style="font-size: 10px; color: var(--text-tertiary);">未完成</span>'}
322
325
  </div>
323
- ${h?'<span style="position: absolute; top: -4px; right: -4px; background: #10b981; color: white; width: 16px; height: 16px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 10px; box-shadow: 0 2px 4px rgba(0,0,0,0.2);">✓</span>':""}
326
+ ${c?'<span style="position: absolute; top: -4px; right: -4px; background: #10b981; color: white; width: 16px; height: 16px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 10px; box-shadow: 0 2px 4px rgba(0,0,0,0.2);">✓</span>':""}
324
327
  </div>
325
328
  `}).join(""):'<p style="margin: 0; color: var(--text-secondary); font-style: italic; font-size: 13px;">未分配给任何人</p>'}
326
329
  </div>
327
330
  </div>
328
331
 
329
332
  <!-- 完成情况统计 -->
330
- ${r.assignedTo&&r.assignedTo.length>0?`
333
+ ${y.assignedTo&&y.assignedTo.length>0?`
331
334
  <div class="detail-section" style="background: var(--bg-secondary); border-radius: 8px; padding: 12px; margin-bottom: 12px;">
332
335
  <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px;">
333
336
  <span style="font-size: 18px;">📈</span>
@@ -335,22 +338,22 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
335
338
  </div>
336
339
  <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 10px;">
337
340
  <div style="background: linear-gradient(135deg, rgba(16, 185, 129, 0.1) 0%, rgba(5, 150, 105, 0.1) 100%); padding: 10px; border-radius: 8px; border: 1px solid rgba(16, 185, 129, 0.2); text-align: center;">
338
- <div style="font-size: 22px; font-weight: 700; color: #10b981;">${r.completedBy?r.completedBy.length:0}</div>
341
+ <div style="font-size: 22px; font-weight: 700; color: #10b981;">${y.completedBy?y.completedBy.length:0}</div>
339
342
  <div style="font-size: 10px; color: var(--text-secondary); margin-top: 2px;">已完成</div>
340
343
  </div>
341
344
  <div style="background: linear-gradient(135deg, rgba(245, 158, 11, 0.1) 0%, rgba(217, 119, 6, 0.1) 100%); padding: 10px; border-radius: 8px; border: 1px solid rgba(245, 158, 11, 0.2); text-align: center;">
342
- <div style="font-size: 22px; font-weight: 700; color: #f59e0b;">${r.assignedTo.length-(r.completedBy?r.completedBy.length:0)}</div>
345
+ <div style="font-size: 22px; font-weight: 700; color: #f59e0b;">${y.assignedTo.length-(y.completedBy?y.completedBy.length:0)}</div>
343
346
  <div style="font-size: 10px; color: var(--text-secondary); margin-top: 2px;">未完成</div>
344
347
  </div>
345
348
  <div style="background: linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(168, 85, 247, 0.1) 100%); padding: 10px; border-radius: 8px; border: 1px solid rgba(99, 102, 241, 0.2); text-align: center;">
346
- <div style="font-size: 22px; font-weight: 700; color: #6366f1;">${Math.round((r.completedBy?r.completedBy.length:0)/r.assignedTo.length*100)}%</div>
349
+ <div style="font-size: 22px; font-weight: 700; color: #6366f1;">${Math.round((y.completedBy?y.completedBy.length:0)/y.assignedTo.length*100)}%</div>
347
350
  <div style="font-size: 10px; color: var(--text-secondary); margin-top: 2px;">完成率</div>
348
351
  </div>
349
352
  </div>
350
353
 
351
354
  <!-- 进度条 -->
352
355
  <div style="background: var(--bg-tertiary); height: 8px; border-radius: 4px; overflow: hidden; position: relative;">
353
- <div style="background: linear-gradient(90deg, #10b981 0%, #059669 100%); height: 100%; width: ${(r.completedBy?r.completedBy.length:0)/r.assignedTo.length*100}%; transition: width 0.3s ease; box-shadow: 0 0 8px rgba(16, 185, 129, 0.5);"></div>
356
+ <div style="background: linear-gradient(90deg, #10b981 0%, #059669 100%); height: 100%; width: ${(y.completedBy?y.completedBy.length:0)/y.assignedTo.length*100}%; transition: width 0.3s ease; box-shadow: 0 0 8px rgba(16, 185, 129, 0.5);"></div>
354
357
  </div>
355
358
  </div>
356
359
  `:""}
@@ -365,7 +368,7 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
365
368
  <h4 style="margin: 0; color: var(--text-secondary); font-size: 12px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;">创建时间</h4>
366
369
  </div>
367
370
  <p style="margin: 0; font-size: 13px; color: var(--text-primary);">
368
- ${new Date(r.createdAt).toLocaleString("zh-CN",{year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}
371
+ ${new Date(y.createdAt).toLocaleString("zh-CN",{year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}
369
372
  </p>
370
373
  </div>
371
374
 
@@ -376,14 +379,14 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
376
379
  <h4 style="margin: 0; color: var(--text-secondary); font-size: 12px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.5px;">更新时间</h4>
377
380
  </div>
378
381
  <p style="margin: 0; font-size: 13px; color: var(--text-primary);">
379
- ${new Date(r.updatedAt).toLocaleString("zh-CN",{year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}
382
+ ${new Date(y.updatedAt).toLocaleString("zh-CN",{year:"numeric",month:"short",day:"numeric",hour:"2-digit",minute:"2-digit"})}
380
383
  </p>
381
384
  </div>
382
385
 
383
386
  </div>
384
387
 
385
388
  <!-- 投票信息 -->
386
- ${r.poll?`
389
+ ${y.poll?`
387
390
  <div class="detail-section" style="background: var(--bg-secondary); border-radius: 8px; padding: 12px; margin-top: 12px;">
388
391
  <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px;">
389
392
  <span style="font-size: 18px;">📊</span>
@@ -391,43 +394,43 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
391
394
  </div>
392
395
 
393
396
  <div style="background: linear-gradient(135deg, rgba(99, 102, 241, 0.05) 0%, rgba(168, 85, 247, 0.05) 100%); border-radius: 8px; padding: 12px; border: 1px solid rgba(99, 102, 241, 0.2);">
394
- <h5 style="margin: 0 0 10px 0; font-size: 14px; font-weight: 600; color: var(--text-primary);">${r.poll.question||"投票"}</h5>
397
+ <h5 style="margin: 0 0 10px 0; font-size: 14px; font-weight: 600; color: var(--text-primary);">${y.poll.question||"投票"}</h5>
395
398
 
396
399
  <!-- 投票统计 -->
397
400
  <div style="display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 12px;">
398
401
  <div style="background: var(--bg-tertiary); padding: 8px; border-radius: 6px; text-align: center;">
399
- <div style="font-size: 20px; font-weight: 700; color: var(--primary);">${r.poll.votedCount||0}</div>
402
+ <div style="font-size: 20px; font-weight: 700; color: var(--primary);">${y.poll.votedCount||0}</div>
400
403
  <div style="font-size: 10px; color: var(--text-secondary); margin-top: 2px;">已投票</div>
401
404
  </div>
402
405
  <div style="background: var(--bg-tertiary); padding: 8px; border-radius: 6px; text-align: center;">
403
- <div style="font-size: 20px; font-weight: 700; color: var(--warning);">${(r.poll.totalMembers||0)-(r.poll.votedCount||0)}</div>
406
+ <div style="font-size: 20px; font-weight: 700; color: var(--warning);">${(y.poll.totalMembers||0)-(y.poll.votedCount||0)}</div>
404
407
  <div style="font-size: 10px; color: var(--text-secondary); margin-top: 2px;">未投票</div>
405
408
  </div>
406
409
  <div style="background: var(--bg-tertiary); padding: 8px; border-radius: 6px; text-align: center;">
407
- <div style="font-size: 20px; font-weight: 700; color: var(--success);">${r.poll.totalVotes||0}</div>
410
+ <div style="font-size: 20px; font-weight: 700; color: var(--success);">${y.poll.totalVotes||0}</div>
408
411
  <div style="font-size: 10px; color: var(--text-secondary); margin-top: 2px;">总票数</div>
409
412
  </div>
410
413
  </div>
411
414
 
412
415
  <!-- 投票选项 -->
413
416
  <div style="display: flex; flex-direction: column; gap: 8px;">
414
- ${((t=r.poll.options)==null?void 0:t.map(c=>{const h=r.poll.totalVotes>0?Math.round((c.votes||0)/r.poll.totalVotes*100):0,E=c.voters||[];return`
417
+ ${((g=y.poll.options)==null?void 0:g.map(s=>{const c=y.poll.totalVotes>0?Math.round((s.votes||0)/y.poll.totalVotes*100):0,v=s.voters||[];return`
415
418
  <div style="background: var(--bg-tertiary); border-radius: 8px; padding: 10px; border: 1px solid var(--border);">
416
419
  <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">
417
- <span style="font-weight: 600; font-size: 13px; color: var(--text-primary);">${c.text}</span>
418
- <span style="font-weight: 700; font-size: 13px; color: var(--primary);">${c.votes||0} 票 (${h}%)</span>
420
+ <span style="font-weight: 600; font-size: 13px; color: var(--text-primary);">${s.text}</span>
421
+ <span style="font-weight: 700; font-size: 13px; color: var(--primary);">${s.votes||0} 票 (${c}%)</span>
419
422
  </div>
420
423
  <div style="height: 6px; background: var(--bg-secondary); border-radius: 3px; overflow: hidden; margin-bottom: 8px;">
421
- <div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${h}%; transition: width 0.3s;"></div>
424
+ <div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${c}%; transition: width 0.3s;"></div>
422
425
  </div>
423
- ${E.length>0?`
426
+ ${v.length>0?`
424
427
  <div style="margin-top: 8px; padding-top: 8px; border-top: 1px solid var(--border);">
425
- <div style="font-size: 10px; color: var(--text-secondary); margin-bottom: 6px;">投票成员 (${E.length}人):</div>
428
+ <div style="font-size: 10px; color: var(--text-secondary); margin-bottom: 6px;">投票成员 (${v.length}人):</div>
426
429
  <div style="display: flex; flex-wrap: wrap; gap: 4px;">
427
- ${E.map(k=>{const A=typeof k=="string"?k:k.username;return`
430
+ ${v.map(w=>{const P=typeof w=="string"?w:w.username;return`
428
431
  <span style="display: inline-flex; align-items: center; gap: 4px; padding: 3px 8px; background: linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(168, 85, 247, 0.1) 100%); border: 1px solid rgba(99, 102, 241, 0.3); border-radius: 10px; font-size: 11px;">
429
- <span style="width: 16px; height: 16px; border-radius: 50%; background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 9px; font-weight: 700;">${A?A[0].toUpperCase():"?"}</span>
430
- <span style="font-weight: 600; color: var(--text-primary);">${A||"未知用户"}</span>
432
+ <span style="width: 16px; height: 16px; border-radius: 50%; background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 9px; font-weight: 700;">${P?P[0].toUpperCase():"?"}</span>
433
+ <span style="font-weight: 600; color: var(--text-primary);">${P||"未知用户"}</span>
431
434
  </span>
432
435
  `}).join("")}
433
436
  </div>
@@ -440,23 +443,23 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
440
443
  </div>
441
444
 
442
445
  <!-- 未投票成员 -->
443
- ${r.poll.unvotedMembers&&r.poll.unvotedMembers.length>0?`
446
+ ${y.poll.unvotedMembers&&y.poll.unvotedMembers.length>0?`
444
447
  <div style="margin-top: 12px; padding: 12px; background: linear-gradient(135deg, rgba(239, 68, 68, 0.05) 0%, rgba(220, 38, 38, 0.05) 100%); border: 1px solid rgba(239, 68, 68, 0.2); border-radius: 10px;">
445
448
  <div style="font-size: 14px; font-weight: 600; color: var(--danger); margin-bottom: 12px; display: flex; align-items: center; gap: 8px;">
446
449
  <span>⚠️</span>
447
- <span>未投票成员 (${r.poll.unvotedMembers.length}人):</span>
450
+ <span>未投票成员 (${y.poll.unvotedMembers.length}人):</span>
448
451
  </div>
449
452
  <div style="display: flex; flex-wrap: wrap; gap: 8px;">
450
- ${r.poll.unvotedMembers.map(c=>{const h=typeof c=="string"?c:c.username;return`
453
+ ${y.poll.unvotedMembers.map(s=>{const c=typeof s=="string"?s:s.username;return`
451
454
  <span style="display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; background: var(--bg-tertiary); border: 2px solid rgba(239, 68, 68, 0.3); border-radius: 12px; font-size: 12px;">
452
- <span style="width: 22px; height: 22px; border-radius: 50%; background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 11px; font-weight: 700;">${h?h[0].toUpperCase():"?"}</span>
453
- <span style="font-weight: 600; color: var(--text-primary);">${h||"未知用户"}</span>
455
+ <span style="width: 22px; height: 22px; border-radius: 50%; background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); display: flex; align-items: center; justify-content: center; color: white; font-size: 11px; font-weight: 700;">${c?c[0].toUpperCase():"?"}</span>
456
+ <span style="font-weight: 600; color: var(--text-primary);">${c||"未知用户"}</span>
454
457
  <span style="font-size: 10px; color: var(--danger); background: rgba(239, 68, 68, 0.1); padding: 2px 6px; border-radius: 8px;">未投票</span>
455
458
  </span>
456
459
  `}).join("")}
457
460
  </div>
458
461
  </div>
459
- `:r.poll.totalMembers>0?`
462
+ `:y.poll.totalMembers>0?`
460
463
  <div style="margin-top: 16px; padding: 12px; background: linear-gradient(135deg, rgba(16, 185, 129, 0.05) 0%, rgba(5, 150, 105, 0.05) 100%); border: 1px solid rgba(16, 185, 129, 0.3); border-radius: 10px; text-align: center; color: var(--success); font-weight: 600;">
461
464
  ✅ 所有成员已完成投票
462
465
  </div>
@@ -466,9 +469,9 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
466
469
  `:""}
467
470
 
468
471
  </div>
469
- `,document.getElementById("taskDetailModal").classList.remove("hidden")}})})),document.getElementById("createTaskBtn").addEventListener("click",()=>{document.getElementById("createTaskModal").classList.remove("hidden")}),document.getElementById("closeTaskModal").addEventListener("click",()=>{document.getElementById("createTaskModal").classList.add("hidden")}),document.getElementById("closeTaskDetailModal").addEventListener("click",()=>{document.getElementById("taskDetailModal").classList.add("hidden")}),document.getElementById("createTaskForm").addEventListener("submit",async s=>{s.preventDefault();const x=new FormData(s.target);try{const r=(await d.getGroup(p._id)).group.members.map(t=>t._id);await d.createTask({title:x.get("title"),description:x.get("description"),groupId:p._id,assignedTo:r,deadline:x.get("deadline")||null}),alert("任务创建成功!已分配给所有群组成员"),await K(o),document.getElementById("createTaskModal").classList.add("hidden")}catch(B){console.error("创建任务错误:",B),alert("创建失败: "+B.message)}})}async function J(o){if(!p){o.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}const i=await d.getDocuments(p._id),s=(await d.getGroup(p._id)).group;o.innerHTML=`
472
+ `,document.getElementById("taskDetailModal").classList.remove("hidden")}})})),document.getElementById("createTaskBtn").addEventListener("click",()=>{document.getElementById("createTaskModal").classList.remove("hidden")}),document.getElementById("closeTaskModal").addEventListener("click",()=>{document.getElementById("createTaskModal").classList.add("hidden")}),document.getElementById("closeTaskDetailModal").addEventListener("click",()=>{document.getElementById("taskDetailModal").classList.add("hidden")}),document.getElementById("createTaskForm").addEventListener("submit",async b=>{b.preventDefault();const k=new FormData(b.target);try{const y=(await a.getGroup(i._id)).group.members.map(g=>g._id);await a.createTask({title:k.get("title"),description:k.get("description"),groupId:i._id,assignedTo:y,deadline:k.get("deadline")||null}),alert("任务创建成功!已分配给所有群组成员"),await _(h),document.getElementById("createTaskModal").classList.add("hidden")}catch(B){console.error("创建任务错误:",B),alert("创建失败: "+B.message)}})}async function R(h){if(!i){h.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}const m=await a.getDocuments(i._id),b=(await a.getGroup(i._id)).group;h.innerHTML=`
470
473
  <div class="view-header">
471
- <h2>📄 共享文档 - ${p.name}</h2>
474
+ <h2>📄 共享文档 - ${i.name}</h2>
472
475
  <button class="btn-primary" id="createDocBtn">➕ 创建文档</button>
473
476
  </div>
474
477
  <div class="documents-list" id="docsList"></div>
@@ -492,13 +495,13 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
492
495
  <div class="form-group">
493
496
  <label>👥 成员编辑权限</label>
494
497
  <div style="max-height: 200px; overflow-y: auto; border: 1px solid var(--border); border-radius: 8px; padding: 10px;">
495
- ${s.members.map(r=>`
498
+ ${b.members.map(y=>`
496
499
  <div style="display: flex; align-items: center; gap: 10px; padding: 8px; background: var(--bg-secondary); border-radius: 6px; margin-bottom: 8px;">
497
- <div class="avatar" style="width: 32px; height: 32px; font-size: 14px;">${r.username[0].toUpperCase()}</div>
498
- <span style="flex: 1;">${r.username}</span>
500
+ <div class="avatar" style="width: 32px; height: 32px; font-size: 14px;">${y.username[0].toUpperCase()}</div>
501
+ <span style="flex: 1;">${y.username}</span>
499
502
  <label style="display: flex; align-items: center; gap: 6px; cursor: pointer;">
500
- <input type="checkbox" name="editableMembers" value="${r._id}" ${r._id===s.admin.toString()?"checked disabled":"checked"} style="width: 18px; height: 18px; cursor: pointer;">
501
- <span style="font-size: 13px;">${r._id===s.admin.toString()?"管理员":"可编辑"}</span>
503
+ <input type="checkbox" name="editableMembers" value="${y._id}" ${y._id===b.admin.toString()?"checked disabled":"checked"} style="width: 18px; height: 18px; cursor: pointer;">
504
+ <span style="font-size: 13px;">${y._id===b.admin.toString()?"管理员":"可编辑"}</span>
502
505
  </label>
503
506
  </div>
504
507
  `).join("")}
@@ -524,36 +527,36 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
524
527
  </div>
525
528
  </div>
526
529
  </div>
527
- `;const x=document.getElementById("docsList");i.documents.length===0?x.innerHTML='<div class="empty-state">暂无共享文档</div>':(i.documents.forEach(r=>{var l;const t=document.createElement("div");t.className="document-card";const e=((l=r.editableMembers)==null?void 0:l.length)||0;t.innerHTML=`
530
+ `;const k=document.getElementById("docsList");m.documents.length===0?k.innerHTML='<div class="empty-state">暂无共享文档</div>':(m.documents.forEach(y=>{var d;const g=document.createElement("div");g.className="document-card";const r=((d=y.editableMembers)==null?void 0:d.length)||0;g.innerHTML=`
528
531
  <div style="display: flex; justify-content: space-between; align-items: start;">
529
532
  <div style="flex: 1;">
530
- <h3>📄 ${r.title}</h3>
533
+ <h3>📄 ${y.title}</h3>
531
534
  <div class="doc-meta" style="display: flex; gap: 15px; margin-top: 8px; font-size: 13px; color: var(--text-secondary);">
532
- <span>👤 创建者: ${r.creator.username}</span>
533
- <span>✏️ ${e} 人可编辑</span>
534
- <span>📅 ${new Date(r.createdAt).toLocaleDateString()}</span>
535
+ <span>👤 创建者: ${y.creator.username}</span>
536
+ <span>✏️ ${r} 人可编辑</span>
537
+ <span>📅 ${new Date(y.createdAt).toLocaleDateString()}</span>
535
538
  </div>
536
539
  </div>
537
540
  <div style="display: flex; gap: 10px; align-items: center;">
538
- <button class="btn-secondary btn-sm" data-id="${r._id}" data-action="manage-permission" title="管理权限" style="padding: 8px 16px;">⚙️ 权限</button>
539
- <button class="btn-primary btn-sm" data-id="${r._id}" data-action="edit-doc" title="编辑文档" style="padding: 8px 16px;">✏️ 编辑</button>
540
- <button class="btn-danger btn-sm" data-id="${r._id}" data-action="delete-doc" title="删除文档" style="padding: 8px 16px;">🗑️ 删除</button>
541
+ <button class="btn-secondary btn-sm" data-id="${y._id}" data-action="manage-permission" title="管理权限" style="padding: 8px 16px;">⚙️ 权限</button>
542
+ <button class="btn-primary btn-sm" data-id="${y._id}" data-action="edit-doc" title="编辑文档" style="padding: 8px 16px;">✏️ 编辑</button>
543
+ <button class="btn-danger btn-sm" data-id="${y._id}" data-action="delete-doc" title="删除文档" style="padding: 8px 16px;">🗑️ 删除</button>
541
544
  </div>
542
545
  </div>
543
- `,x.appendChild(t)}),document.querySelectorAll('[data-action="edit-doc"]').forEach(r=>{r.addEventListener("click",()=>{de(o,r.dataset.id)})}),document.querySelectorAll('[data-action="manage-permission"]').forEach(r=>{r.addEventListener("click",async()=>{const t=r.dataset.id,e=i.documents.find(l=>l._id===t);B(e,s)})}),document.querySelectorAll('[data-action="delete-doc"]').forEach(r=>{r.addEventListener("click",async t=>{t.stopPropagation();const e=r.dataset.id;if(confirm("确定要删除这个文档吗?删除后无法恢复!"))try{await d.deleteDocument(e),alert("文档删除成功!"),await J(o)}catch(l){console.error("删除文档错误:",l),alert("删除失败: "+(l.message||"未知错误"))}})})),document.getElementById("createDocBtn").addEventListener("click",()=>{document.getElementById("createDocModal").classList.remove("hidden")}),document.getElementById("closeDocModal").addEventListener("click",()=>{document.getElementById("createDocModal").classList.add("hidden")}),document.getElementById("closeDocModalBtn").addEventListener("click",()=>{document.getElementById("createDocModal").classList.add("hidden")});function B(r,t){const e=document.getElementById("editPermissionModal"),l=document.getElementById("permissionContent");l.innerHTML=`
546
+ `,k.appendChild(g)}),document.querySelectorAll('[data-action="edit-doc"]').forEach(y=>{y.addEventListener("click",()=>{ae(h,y.dataset.id)})}),document.querySelectorAll('[data-action="manage-permission"]').forEach(y=>{y.addEventListener("click",async()=>{const g=y.dataset.id,r=m.documents.find(d=>d._id===g);B(r,b)})}),document.querySelectorAll('[data-action="delete-doc"]').forEach(y=>{y.addEventListener("click",async g=>{g.stopPropagation();const r=y.dataset.id;if(confirm("确定要删除这个文档吗?删除后无法恢复!"))try{await a.deleteDocument(r),alert("文档删除成功!"),await R(h)}catch(d){console.error("删除文档错误:",d),alert("删除失败: "+(d.message||"未知错误"))}})})),document.getElementById("createDocBtn").addEventListener("click",()=>{document.getElementById("createDocModal").classList.remove("hidden")}),document.getElementById("closeDocModal").addEventListener("click",()=>{document.getElementById("createDocModal").classList.add("hidden")}),document.getElementById("closeDocModalBtn").addEventListener("click",()=>{document.getElementById("createDocModal").classList.add("hidden")});function B(y,g){const r=document.getElementById("editPermissionModal"),d=document.getElementById("permissionContent");d.innerHTML=`
544
547
  <div style="margin-bottom: 16px;">
545
- <h4 style="margin: 0 0 8px 0;">📄 ${r.title}</h4>
548
+ <h4 style="margin: 0 0 8px 0;">📄 ${y.title}</h4>
546
549
  <p style="font-size: 13px; color: var(--text-secondary); margin: 0;">管理哪些成员可以编辑此文档</p>
547
550
  </div>
548
551
  <form id="updatePermissionForm">
549
552
  <div style="max-height: 300px; overflow-y: auto; border: 1px solid var(--border); border-radius: 8px; padding: 10px;">
550
- ${t.members.map(c=>{var k;const h=((k=r.editableMembers)==null?void 0:k.includes(c._id))||c._id===t.admin.toString(),E=c._id===t.admin.toString();return`
553
+ ${g.members.map(s=>{var w;const c=((w=y.editableMembers)==null?void 0:w.includes(s._id))||s._id===g.admin.toString(),v=s._id===g.admin.toString();return`
551
554
  <div style="display: flex; align-items: center; gap: 10px; padding: 8px; background: var(--bg-secondary); border-radius: 6px; margin-bottom: 8px;">
552
- <div class="avatar" style="width: 32px; height: 32px; font-size: 14px;">${c.username[0].toUpperCase()}</div>
553
- <span style="flex: 1;">${c.username}</span>
555
+ <div class="avatar" style="width: 32px; height: 32px; font-size: 14px;">${s.username[0].toUpperCase()}</div>
556
+ <span style="flex: 1;">${s.username}</span>
554
557
  <label style="display: flex; align-items: center; gap: 6px; cursor: pointer;">
555
- <input type="checkbox" name="editableMembers" value="${c._id}" ${h?"checked":""} ${E?"disabled":""} style="width: 18px; height: 18px; cursor: pointer;">
556
- <span style="font-size: 13px;">${E?"管理员":"可编辑"}</span>
558
+ <input type="checkbox" name="editableMembers" value="${s._id}" ${c?"checked":""} ${v?"disabled":""} style="width: 18px; height: 18px; cursor: pointer;">
559
+ <span style="font-size: 13px;">${v?"管理员":"可编辑"}</span>
557
560
  </label>
558
561
  </div>
559
562
  `}).join("")}
@@ -564,27 +567,27 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
564
567
  <button type="button" class="btn-secondary" id="cancelPermissionBtn" style="flex: 1;">取消</button>
565
568
  </div>
566
569
  </form>
567
- `,e.classList.remove("hidden"),document.getElementById("cancelPermissionBtn").addEventListener("click",()=>{e.classList.add("hidden")}),document.getElementById("updatePermissionForm").addEventListener("submit",async c=>{c.preventDefault();const E=new FormData(c.target).getAll("editableMembers");try{await d.updateDocumentPermissions(r._id,E),alert("权限更新成功!"),e.classList.add("hidden"),await J(o)}catch(k){console.error("更新权限错误:",k),alert("更新失败: "+k.message)}})}document.getElementById("closePermissionModal").addEventListener("click",()=>{document.getElementById("editPermissionModal").classList.add("hidden")}),document.getElementById("createDocForm").addEventListener("submit",async r=>{r.preventDefault();const t=new FormData(r.target),e=t.getAll("editableMembers");try{await d.createDocument(t.get("title"),t.get("content"),p._id,e),alert("文档创建成功!"),await J(o),document.getElementById("createDocModal").classList.add("hidden")}catch(l){console.error("创建文档错误:",l),alert("创建失败: "+l.message)}})}async function de(o,i){const s=(await d.getDocument(i)).document;o.innerHTML=`
570
+ `,r.classList.remove("hidden"),document.getElementById("cancelPermissionBtn").addEventListener("click",()=>{r.classList.add("hidden")}),document.getElementById("updatePermissionForm").addEventListener("submit",async s=>{s.preventDefault();const v=new FormData(s.target).getAll("editableMembers");try{await a.updateDocumentPermissions(y._id,v),alert("权限更新成功!"),r.classList.add("hidden"),await R(h)}catch(w){console.error("更新权限错误:",w),alert("更新失败: "+w.message)}})}document.getElementById("closePermissionModal").addEventListener("click",()=>{document.getElementById("editPermissionModal").classList.add("hidden")}),document.getElementById("createDocForm").addEventListener("submit",async y=>{y.preventDefault();const g=new FormData(y.target),r=g.getAll("editableMembers");try{await a.createDocument(g.get("title"),g.get("content"),i._id,r),alert("文档创建成功!"),await R(h),document.getElementById("createDocModal").classList.add("hidden")}catch(d){console.error("创建文档错误:",d),alert("创建失败: "+d.message)}})}async function ae(h,m){const b=(await a.getDocument(m)).document;h.innerHTML=`
568
571
  <div class="view-header">
569
572
  <button class="btn-back" id="backBtn">← 返回</button>
570
- <h2>${s.title}</h2>
571
- <span class="doc-status">${s.permission==="readonly"?"🔒 只读模式":"✏️ 编辑模式"}</span>
573
+ <h2>${b.title}</h2>
574
+ <span class="doc-status">${b.permission==="readonly"?"🔒 只读模式":"✏️ 编辑模式"}</span>
572
575
  </div>
573
576
  <div class="editor-container">
574
577
  <div class="editor-toolbar">
575
578
  <div class="online-users" id="onlineUsers">
576
- <span class="user-badge">👤 ${_.username}</span>
579
+ <span class="user-badge">👤 ${t.username}</span>
577
580
  </div>
578
581
  <button class="btn-primary" id="saveBtn">保存</button>
579
582
  </div>
580
583
  <div id="editor"></div>
581
584
  <div class="editor-footer">
582
- <span>最后编辑: ${new Date(s.updatedAt).toLocaleString()}</span>
585
+ <span>最后编辑: ${new Date(b.updatedAt).toLocaleString()}</span>
583
586
  </div>
584
587
  </div>
585
- `;const x=new Quill("#editor",{theme:"snow",modules:{toolbar:[[{header:[1,2,3,!1]}],["bold","italic","underline","strike"],[{list:"ordered"},{list:"bullet"}],[{color:[]},{background:[]}],["link","image","code-block"],["clean"]]},readOnly:!1});x.root.innerHTML=s.content||"";let B,r;x.on("text-change",()=>{clearTimeout(B),clearTimeout(r),a.sendTyping(i,_.username,!0),B=setTimeout(()=>{a.sendTyping(i,_.username,!1)},1e3),r=setTimeout(async()=>{const t=x.root.innerHTML;try{await d.updateDocument(i,t)}catch(e){console.error("自动保存失败:",e)}},2e3)}),document.getElementById("saveBtn").addEventListener("click",async()=>{try{const t=x.root.innerHTML;await d.updateDocument(i,t),alert("保存成功!")}catch(t){alert("保存失败: "+t.message)}}),a.on("document_update",t=>{t.documentId===i&&t.userId!==_.id&&(x.root.innerHTML=t.content)}),a.on("typing",t=>{if(t.documentId===i&&t.userId!==_.id){const e=document.getElementById("onlineUsers");if(t.isTyping)e.innerHTML+=`<span class="user-badge typing" data-user="${t.userId}">✏️ ${t.username}</span>`;else{const l=e.querySelector(`[data-user="${t.userId}"]`);l&&l.remove()}}}),document.getElementById("backBtn").addEventListener("click",()=>{J(o)})}async function X(o){if(!p){o.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const i=await d.getGroupFiles(p._id);o.innerHTML=`
588
+ `;const k=new Quill("#editor",{theme:"snow",modules:{toolbar:[[{header:[1,2,3,!1]}],["bold","italic","underline","strike"],[{list:"ordered"},{list:"bullet"}],[{color:[]},{background:[]}],["link","image","code-block"],["clean"]]},readOnly:!1});k.root.innerHTML=b.content||"";let B,y;k.on("text-change",()=>{clearTimeout(B),clearTimeout(y),e.sendTyping(m,t.username,!0),B=setTimeout(()=>{e.sendTyping(m,t.username,!1)},1e3),y=setTimeout(async()=>{const g=k.root.innerHTML;try{await a.updateDocument(m,g)}catch(r){console.error("自动保存失败:",r)}},2e3)}),document.getElementById("saveBtn").addEventListener("click",async()=>{try{const g=k.root.innerHTML;await a.updateDocument(m,g),alert("保存成功!")}catch(g){alert("保存失败: "+g.message)}}),e.on("document_update",g=>{g.documentId===m&&g.userId!==t.id&&(k.root.innerHTML=g.content)}),e.on("typing",g=>{if(g.documentId===m&&g.userId!==t.id){const r=document.getElementById("onlineUsers");if(g.isTyping)r.innerHTML+=`<span class="user-badge typing" data-user="${g.userId}">✏️ ${g.username}</span>`;else{const d=r.querySelector(`[data-user="${g.userId}"]`);d&&d.remove()}}}),document.getElementById("backBtn").addEventListener("click",()=>{R(h)})}async function Z(h){if(!i){h.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const m=await a.getGroupFiles(i._id);h.innerHTML=`
586
589
  <div class="view-header">
587
- <h2>文件管理 - ${p.name}</h2>
590
+ <h2>文件管理 - ${i.name}</h2>
588
591
  <button class="btn-primary" id="uploadFileBtn">📤 上传文件</button>
589
592
  </div>
590
593
  <div class="files-list" id="filesList"></div>
@@ -613,29 +616,29 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
613
616
  </form>
614
617
  </div>
615
618
  </div>
616
- `;const n=document.getElementById("filesList");!i.files||i.files.length===0?n.innerHTML='<div class="empty-state">暂无文件</div>':(i.files.forEach(s=>{const x=document.createElement("div");x.className="file-card";const B=le(s.mimetype),r=ce(s.size);x.innerHTML=`
619
+ `;const u=document.getElementById("filesList");!m.files||m.files.length===0?u.innerHTML='<div class="empty-state">暂无文件</div>':(m.files.forEach(b=>{const k=document.createElement("div");k.className="file-card";const B=se(b.mimetype),y=le(b.size);k.innerHTML=`
617
620
  <div class="file-icon">${B}</div>
618
621
  <div class="file-info">
619
- <h4>${s.originalName}</h4>
622
+ <h4>${b.originalName}</h4>
620
623
  <div class="file-meta">
621
- <span>上传者: ${s.uploader.username}</span>
622
- <span>大小: ${r}</span>
623
- <span>时间: ${new Date(s.createdAt).toLocaleString()}</span>
624
+ <span>上传者: ${b.uploader.username}</span>
625
+ <span>大小: ${y}</span>
626
+ <span>时间: ${new Date(b.createdAt).toLocaleString()}</span>
624
627
  </div>
625
- ${s.description?`<p class="file-description">${s.description}</p>`:""}
628
+ ${b.description?`<p class="file-description">${b.description}</p>`:""}
626
629
  </div>
627
630
  <div class="file-actions">
628
- <a href="${d.getFileDownloadUrl(s._id)}" class="btn-primary" download>下载</a>
629
- <button class="btn-danger" data-id="${s._id}" data-action="delete-file">删除</button>
631
+ <a href="${a.getFileDownloadUrl(b._id)}" class="btn-primary" download>下载</a>
632
+ <button class="btn-danger" data-id="${b._id}" data-action="delete-file">删除</button>
630
633
  </div>
631
- `,n.appendChild(x)}),document.querySelectorAll('[data-action="delete-file"]').forEach(s=>{s.addEventListener("click",async()=>{if(confirm("确定要删除这个文件吗?"))try{await d.deleteFile(s.dataset.id),alert("文件删除成功!"),await X(o)}catch(x){alert("删除失败: "+x.message)}})})),document.getElementById("uploadFileBtn").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.remove("hidden")}),document.getElementById("closeUploadModal").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset()}),document.getElementById("cancelUpload").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset()}),document.getElementById("uploadFileForm").addEventListener("submit",async s=>{s.preventDefault();const x=document.getElementById("fileInput"),B=document.getElementById("fileDescription").value;if(!x.files[0]){alert("请选择文件");return}try{await d.uploadFile(p._id,x.files[0],B),alert("文件上传成功!"),document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset(),await X(o)}catch(r){alert("上传失败: "+r.message)}})}catch(i){console.error("获取文件列表失败:",i),o.innerHTML=`
634
+ `,u.appendChild(k)}),document.querySelectorAll('[data-action="delete-file"]').forEach(b=>{b.addEventListener("click",async()=>{if(confirm("确定要删除这个文件吗?"))try{await a.deleteFile(b.dataset.id),alert("文件删除成功!"),await Z(h)}catch(k){alert("删除失败: "+k.message)}})})),document.getElementById("uploadFileBtn").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.remove("hidden")}),document.getElementById("closeUploadModal").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset()}),document.getElementById("cancelUpload").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset()}),document.getElementById("uploadFileForm").addEventListener("submit",async b=>{b.preventDefault();const k=document.getElementById("fileInput"),B=document.getElementById("fileDescription").value;if(!k.files[0]){alert("请选择文件");return}try{await a.uploadFile(i._id,k.files[0],B),alert("文件上传成功!"),document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset(),await Z(h)}catch(y){alert("上传失败: "+y.message)}})}catch(m){console.error("获取文件列表失败:",m),h.innerHTML=`
632
635
  <div class="view-header">
633
636
  <h2>文件管理</h2>
634
637
  </div>
635
- <div class="empty-state">加载文件失败: ${i.message}</div>
636
- `}}function le(o){return o.startsWith("image/")?"🖼️":o==="application/pdf"?"📕":o.includes("word")||o.includes("document")?"📘":o.includes("excel")||o.includes("spreadsheet")?"📗":o.includes("zip")||o.includes("compressed")?"📦":"📄"}function ce(o){if(o===0)return"0 Bytes";const i=1024,n=["Bytes","KB","MB","GB"],s=Math.floor(Math.log(o)/Math.log(i));return Math.round(o/Math.pow(i,s)*100)/100+" "+n[s]}async function pe(o){if(!p){o.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}let n=(await d.getGroup(p._id)).group;o.innerHTML=`
638
+ <div class="empty-state">加载文件失败: ${m.message}</div>
639
+ `}}function se(h){return h.startsWith("image/")?"🖼️":h==="application/pdf"?"📕":h.includes("word")||h.includes("document")?"📘":h.includes("excel")||h.includes("spreadsheet")?"📗":h.includes("zip")||h.includes("compressed")?"📦":"📄"}function le(h){if(h===0)return"0 Bytes";const m=1024,u=["Bytes","KB","MB","GB"],b=Math.floor(Math.log(h)/Math.log(m));return Math.round(h/Math.pow(m,b)*100)/100+" "+u[b]}async function U(h){if(!i){h.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}let u=(await a.getGroup(i._id)).group;h.innerHTML=`
637
640
  <div class="view-header">
638
- <h2>群聊 - ${p.name}</h2>
641
+ <h2>群聊 - ${i.name}</h2>
639
642
  <div style="display: flex; gap: 10px;">
640
643
  <button class="btn-secondary" id="muteAllBtn">全体禁言</button>
641
644
  <button class="btn-secondary" id="manageMuteBtn">个人禁言</button>
@@ -776,43 +779,43 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
776
779
  </div>
777
780
  </div>
778
781
  </div>
779
- `;const s=document.getElementById("messages"),x=document.getElementById("messageInput"),B=document.getElementById("sendBtn"),r=document.getElementById("emojiBtn"),t=document.getElementById("emojiPicker");let e=new Set((n.mutedUsers||[]).map(String)),l=!!n.mutedAll;r.addEventListener("click",()=>{t.classList.toggle("hidden")}),t.addEventListener("emoji-click",g=>{x.value+=g.detail.unicode,x.focus(),t.classList.add("hidden")}),document.addEventListener("click",g=>{!r.contains(g.target)&&!t.contains(g.target)&&t.classList.add("hidden")}),"Notification"in window&&Notification.permission==="default"&&Notification.requestPermission();try{const g=await d.getGroupMessages(p._id);g.messages&&(g.messages.forEach(I=>{const S=document.createElement("div");S.className=`message ${I.sender===D?"own":""}`;let L=I.content,H=!1;if(I.content.startsWith("[白板作品]")){H=!0;let P=I.content.replace("[白板作品]","").trim();if(P.includes("/api/files/")&&!P.includes("token=")){const q=localStorage.getItem("token");P=P.includes("?")?`${P}&token=${q}`:`${P}?token=${q}`}console.log("白板作品URL:",P.substring(0,100)),L=`
782
+ `;const b=document.getElementById("messages"),k=document.getElementById("messageInput"),B=document.getElementById("sendBtn"),y=document.getElementById("emojiBtn"),g=document.getElementById("emojiPicker");let r=new Set((u.mutedUsers||[]).map(String)),d=!!u.mutedAll;y.addEventListener("click",()=>{g.classList.toggle("hidden")}),g.addEventListener("emoji-click",p=>{k.value+=p.detail.unicode,k.focus(),g.classList.add("hidden")}),document.addEventListener("click",p=>{!y.contains(p.target)&&!g.contains(p.target)&&g.classList.add("hidden")}),"Notification"in window&&Notification.permission==="default"&&Notification.requestPermission();try{const p=await a.getGroupMessages(i._id);p.messages&&(p.messages.forEach(L=>{const j=document.createElement("div");j.className=`message ${L.sender===o?"own":""}`;let C=L.content,G=!1;if(L.content.startsWith("[白板作品]")){G=!0;let q=L.content.replace("[白板作品]","").trim();if(q.includes("/api/files/")&&!q.includes("token=")){const Y=localStorage.getItem("token");q=q.includes("?")?`${q}&token=${Y}`:`${q}?token=${Y}`}console.log("白板作品URL:",q.substring(0,100)),C=`
780
783
  <div style="margin-bottom: 12px; font-weight: 600; color: white; font-size: 15px;">🎨 白板作品</div>
781
- <img src="${P}" alt="白板作品" style="max-width: 400px; max-height: 300px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); cursor: pointer; transition: transform 0.2s;" onclick="showImageModal('${P.replace(/'/g,"\\'")}');" onmouseover="this.style.transform='scale(1.02)'" onmouseout="this.style.transform='scale(1)'">
784
+ <img src="${q}" alt="白板作品" style="max-width: 400px; max-height: 300px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); cursor: pointer; transition: transform 0.2s;" onclick="showImageModal('${q.replace(/'/g,"\\'")}');" onmouseover="this.style.transform='scale(1.02)'" onmouseout="this.style.transform='scale(1)'">
782
785
  <div style="margin-top: 10px; font-size: 12px; color: rgba(255,255,255,0.8);">点击查看大图</div>
783
- `}S.innerHTML=`
786
+ `}j.innerHTML=`
784
787
  <div class="message-header">
785
- <span class="message-user">${I.username}</span>
786
- <span class="message-time">${new Date(I.timestamp).toLocaleTimeString()}</span>
788
+ <span class="message-user">${L.username}</span>
789
+ <span class="message-time">${new Date(L.timestamp).toLocaleTimeString()}</span>
787
790
  </div>
788
- <div class="message-content" style="${H?"background: linear-gradient(135deg, rgb(99, 102, 241) 0%, rgb(139, 92, 246) 100%); color: white; padding: 16px; border-radius: 12px; box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);":""}">${L}</div>
789
- `,s.appendChild(S)}),s.scrollTop=s.scrollHeight)}catch(g){console.error("加载历史消息失败:",g)}const c=()=>{const g=document.getElementById("muteAllBtn");g.textContent=l?"取消全体禁言":"全体禁言",g.style.background=l?"var(--danger)":""};c(),document.getElementById("clearChatBtn").addEventListener("click",async()=>{if(!confirm(`⚠️ 警告:此操作将永久删除该群组的所有聊天记录!
791
+ <div class="message-content" style="${G?"background: linear-gradient(135deg, rgb(99, 102, 241) 0%, rgb(139, 92, 246) 100%); color: white; padding: 16px; border-radius: 12px; box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);":""}">${C}</div>
792
+ `,b.appendChild(j)}),b.scrollTop=b.scrollHeight)}catch(p){console.error("加载历史消息失败:",p)}const s=()=>{const p=document.getElementById("muteAllBtn");p.textContent=d?"取消全体禁言":"全体禁言",p.style.background=d?"var(--danger)":""};s(),document.getElementById("clearChatBtn").addEventListener("click",async()=>{if(!confirm(`⚠️ 警告:此操作将永久删除该群组的所有聊天记录!
790
793
 
791
794
  确定要清除吗?`))return;if(prompt(`请输入群组名称以确认删除:
792
795
 
793
- 群组名称:`+p.name)!==p.name){alert("❌ 群组名称不匹配,操作已取消");return}try{const S=document.getElementById("clearChatBtn"),L=S.innerHTML;S.innerHTML="⏳ 清除中...",S.disabled=!0;const H=localStorage.getItem("token"),P=await fetch(`http://localhost:8765/api/messages/group/${p._id}/clear`,{method:"DELETE",headers:{Authorization:`Bearer ${H}`,"Content-Type":"application/json"}});if(!P.ok){const U=await P.json();throw new Error(U.message||"清除失败")}const q=await P.json();s.innerHTML='<div class="empty-state" style="padding: 40px; text-align: center; color: var(--text-secondary);">✨ 聊天记录已清空</div>',alert(`✅ 成功清除 ${q.deletedCount||0} 条聊天记录!`),S.innerHTML=L,S.disabled=!1}catch(S){console.error("清除聊天记录失败:",S),alert("❌ 清除失败: "+S.message);const L=document.getElementById("clearChatBtn");L.innerHTML="🗑️ 清除记录",L.disabled=!1}}),document.getElementById("muteAllBtn").addEventListener("click",async()=>{try{const g=!l;l=!!(await d.setMuteAll(p._id,g)).mutedAll,c();const S=document.createElement("div");S.className="notification",S.textContent=l?"已开启全体禁言(成员无法发言)":"已取消全体禁言",s.appendChild(S),s.scrollTop=s.scrollHeight}catch(g){alert("设置失败: "+g.message)}}),document.getElementById("manageMuteBtn").addEventListener("click",async()=>{n=(await d.getGroup(p._id)).group,e=new Set((n.mutedUsers||[]).map(String));const I=document.getElementById("membersList");I.innerHTML=n.members.filter(S=>S._id.toString()!==D).map(S=>{const L=e.has(S._id.toString());return`
796
+ 群组名称:`+i.name)!==i.name){alert("❌ 群组名称不匹配,操作已取消");return}try{const j=document.getElementById("clearChatBtn"),C=j.innerHTML;j.innerHTML="⏳ 清除中...",j.disabled=!0;const G=localStorage.getItem("token"),q=await fetch(`http://localhost:8765/api/messages/group/${i._id}/clear`,{method:"DELETE",headers:{Authorization:`Bearer ${G}`,"Content-Type":"application/json"}});if(!q.ok){const X=await q.json();throw new Error(X.message||"清除失败")}const Y=await q.json();b.innerHTML='<div class="empty-state" style="padding: 40px; text-align: center; color: var(--text-secondary);">✨ 聊天记录已清空</div>',alert(`✅ 成功清除 ${Y.deletedCount||0} 条聊天记录!`),j.innerHTML=C,j.disabled=!1}catch(j){console.error("清除聊天记录失败:",j),alert("❌ 清除失败: "+j.message);const C=document.getElementById("clearChatBtn");C.innerHTML="🗑️ 清除记录",C.disabled=!1}}),document.getElementById("muteAllBtn").addEventListener("click",async()=>{try{const p=!d;d=!!(await a.setMuteAll(i._id,p)).mutedAll,s();const j=document.createElement("div");j.className="notification",j.textContent=d?"已开启全体禁言(成员无法发言)":"已取消全体禁言",b.appendChild(j),b.scrollTop=b.scrollHeight}catch(p){alert("设置失败: "+p.message)}}),document.getElementById("manageMuteBtn").addEventListener("click",async()=>{u=(await a.getGroup(i._id)).group,r=new Set((u.mutedUsers||[]).map(String));const L=document.getElementById("membersList");L.innerHTML=u.members.filter(j=>j._id.toString()!==o).map(j=>{const C=r.has(j._id.toString());return`
794
797
  <div style="display: flex; align-items: center; justify-content: space-between; padding: 12px; border-bottom: 1px solid var(--border);">
795
798
  <div style="display: flex; align-items: center; gap: 10px;">
796
- <div class="avatar" style="width: 35px; height: 35px;">${S.username[0].toUpperCase()}</div>
797
- <span>${S.username}</span>
799
+ <div class="avatar" style="width: 35px; height: 35px;">${j.username[0].toUpperCase()}</div>
800
+ <span>${j.username}</span>
798
801
  </div>
799
- <button class="btn-secondary btn-sm" onclick="toggleMute('${S._id}')" id="mute-${S._id}">
800
- ${L?"取消禁言":"禁言"}
802
+ <button class="btn-secondary btn-sm" onclick="toggleMute('${j._id}')" id="mute-${j._id}">
803
+ ${C?"取消禁言":"禁言"}
801
804
  </button>
802
805
  </div>
803
- `}).join(""),document.getElementById("manageMuteModal").classList.remove("hidden")}),document.getElementById("closeMuteModal").addEventListener("click",()=>{document.getElementById("manageMuteModal").classList.add("hidden")}),window.toggleMute=async g=>{try{const I=!e.has(g),S=await d.setUserMute(p._id,g,I);e=new Set((S.mutedUsers||[]).map(String));const L=document.getElementById(`mute-${g}`);L.textContent=e.has(g)?"取消禁言":"禁言",L.style.background=e.has(g)?"var(--danger)":""}catch(I){alert("操作失败: "+I.message)}},a.on("chat_message",g=>{if(g.groupId===p._id){const I=document.createElement("div");I.className=`message ${g.userId===D?"own":""}`;let S=g.content,L=!1;if(g.content.startsWith("[白板作品]")){L=!0;let H=g.content.replace("[白板作品]","").trim();if(H.includes("/api/files/")&&!H.includes("token=")){const P=localStorage.getItem("token");H=H.includes("?")?`${H}&token=${P}`:`${H}?token=${P}`}console.log("实时白板作品URL:",H.substring(0,100)),S=`
806
+ `}).join(""),document.getElementById("manageMuteModal").classList.remove("hidden")}),document.getElementById("closeMuteModal").addEventListener("click",()=>{document.getElementById("manageMuteModal").classList.add("hidden")}),window.toggleMute=async p=>{try{const L=!r.has(p),j=await a.setUserMute(i._id,p,L);r=new Set((j.mutedUsers||[]).map(String));const C=document.getElementById(`mute-${p}`);C.textContent=r.has(p)?"取消禁言":"禁言",C.style.background=r.has(p)?"var(--danger)":""}catch(L){alert("操作失败: "+L.message)}},e.on("chat_message",p=>{if(p.groupId===i._id){const L=document.createElement("div");L.className=`message ${p.userId===o?"own":""}`;let j=p.content,C=!1;if(p.content.startsWith("[白板作品]")){C=!0;let G=p.content.replace("[白板作品]","").trim();if(G.includes("/api/files/")&&!G.includes("token=")){const q=localStorage.getItem("token");G=G.includes("?")?`${G}&token=${q}`:`${G}?token=${q}`}console.log("实时白板作品URL:",G.substring(0,100)),j=`
804
807
  <div style="margin-bottom: 12px; font-weight: 600; color: white; font-size: 15px;">🎨 白板作品</div>
805
- <img src="${H}" alt="白板作品" style="max-width: 400px; max-height: 300px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); cursor: pointer; transition: transform 0.2s;" onclick="showImageModal('${H.replace(/'/g,"\\'")}');" onmouseover="this.style.transform='scale(1.02)'" onmouseout="this.style.transform='scale(1)'">
808
+ <img src="${G}" alt="白板作品" style="max-width: 400px; max-height: 300px; border-radius: 12px; box-shadow: 0 4px 12px rgba(0,0,0,0.15); cursor: pointer; transition: transform 0.2s;" onclick="showImageModal('${G.replace(/'/g,"\\'")}');" onmouseover="this.style.transform='scale(1.02)'" onmouseout="this.style.transform='scale(1)'">
806
809
  <div style="margin-top: 10px; font-size: 12px; color: rgba(255,255,255,0.8);">点击查看大图</div>
807
- `}I.innerHTML=`
810
+ `}L.innerHTML=`
808
811
  <div class="message-header">
809
- <span class="message-user">${g.username}</span>
810
- <span class="message-time">${new Date(g.timestamp).toLocaleTimeString()}</span>
812
+ <span class="message-user">${p.username}</span>
813
+ <span class="message-time">${new Date(p.timestamp).toLocaleTimeString()}</span>
811
814
  </div>
812
- <div class="message-content" style="${L?"background: linear-gradient(135deg, rgb(99, 102, 241) 0%, rgb(139, 92, 246) 100%); color: white; padding: 16px; border-radius: 12px; box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);":""}">${S}</div>
813
- `,s.appendChild(I),s.scrollTop=s.scrollHeight}}),a.on("chat_blocked",g=>{if(g.groupId===p._id){const I=document.createElement("div");I.className="notification",I.textContent=g.message||"消息发送失败",s.appendChild(I),s.scrollTop=s.scrollHeight}});const h=()=>{const g=x.value.trim();g&&(a.sendChatMessage(p._id,_.username,g),x.value="")};B.addEventListener("click",h),x.addEventListener("keypress",g=>{g.key==="Enter"&&h()}),document.getElementById("openAIBtn").addEventListener("click",()=>{document.getElementById("aiModal").classList.remove("hidden")}),document.getElementById("closeAIModal").addEventListener("click",()=>{document.getElementById("aiModal").classList.add("hidden")}),document.querySelectorAll(".quick-question-btn").forEach(g=>{g.addEventListener("click",()=>{document.getElementById("aiInputText").value=g.dataset.question,document.getElementById("aiSendBtnModal").click()}),g.addEventListener("mouseenter",I=>{I.target.style.background="var(--primary)",I.target.style.color="white",I.target.style.transform="translateY(-2px)"}),g.addEventListener("mouseleave",I=>{I.target.style.background="var(--bg)",I.target.style.color="inherit",I.target.style.transform="translateY(0)"})});const E=document.getElementById("aiInputText");E.addEventListener("input",()=>{E.style.height="auto",E.style.height=E.scrollHeight+"px"}),E.addEventListener("keydown",g=>{g.key==="Enter"&&!g.shiftKey&&(g.preventDefault(),document.getElementById("aiSendBtnModal").click())}),E.addEventListener("focus",()=>{E.style.borderColor="var(--primary)"}),E.addEventListener("blur",()=>{E.style.borderColor="var(--border)"});const k=document.getElementById("aiSendBtnModal");k.addEventListener("mouseenter",()=>{k.style.transform="scale(1.05)"}),k.addEventListener("mouseleave",()=>{k.style.transform="scale(1)"}),document.getElementById("aiSendBtnModal").addEventListener("click",async()=>{const g=document.getElementById("aiInputText"),I=g.value.trim();if(!I)return;const S=document.getElementById("aiChatMessages"),L=document.createElement("div");L.className="ai-message user",L.style.cssText="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 12px 18px; border-radius: 18px 18px 4px 18px; margin: 10px 0; max-width: 75%; margin-left: auto; text-align: right; box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); animation: slideInRight 0.3s ease;",L.textContent=I,S.appendChild(L),g.value="";const H=document.createElement("div");H.className="ai-message ai loading",H.style.cssText="background: var(--bg-tertiary); padding: 12px 16px; border-radius: 12px; margin: 10px 0; max-width: 70%;",H.textContent="思考中...",S.appendChild(H),S.scrollTop=S.scrollHeight;try{const P=localStorage.getItem("token"),U=await(await fetch("http://localhost:8765/api/ai/ask",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${P}`},body:JSON.stringify({question:I,groupId:p==null?void 0:p._id})})).json();H.remove();const G=document.createElement("div");G.className="ai-message ai",G.style.cssText="background: var(--bg-secondary); padding: 15px 18px; border-radius: 18px 18px 18px 4px; margin: 10px 0; max-width: 75%; border: 1px solid var(--border); box-shadow: 0 2px 4px rgba(0,0,0,0.05); animation: slideInLeft 0.3s ease; line-height: 1.6;",G.textContent=U.answer||"抱歉,我无法回答这个问题。",S.appendChild(G),S.scrollTop=S.scrollHeight}catch(P){H.remove();const q=document.createElement("div");q.className="ai-message ai error",q.style.cssText="background: var(--danger); color: white; padding: 12px 16px; border-radius: 12px; margin: 10px 0; max-width: 70%;",q.textContent="抱歉,发生了错误: "+P.message,S.appendChild(q),S.scrollTop=S.scrollHeight}}),window.showImageModal=g=>{const I=document.getElementById("imagePreviewModal"),S=document.getElementById("previewImage"),L=document.getElementById("downloadImageBtn");S.src=g,I.classList.remove("hidden"),L.onclick=async()=>{try{const P=await(await fetch(g)).blob(),q=window.URL.createObjectURL(P),U=document.createElement("a");U.href=q,U.download=`whiteboard-${Date.now()}.png`,document.body.appendChild(U),U.click(),document.body.removeChild(U),window.URL.revokeObjectURL(q)}catch(H){console.error("下载失败:",H),alert("下载失败,请重试")}}},document.getElementById("closeImagePreview").addEventListener("click",()=>{document.getElementById("imagePreviewModal").classList.add("hidden")}),document.getElementById("imagePreviewModal").addEventListener("click",g=>{g.target.id==="imagePreviewModal"&&document.getElementById("imagePreviewModal").classList.add("hidden")}),document.getElementById("openWhiteboardBtn").addEventListener("click",()=>{document.getElementById("whiteboardModal").classList.remove("hidden"),A()}),document.getElementById("closeWhiteboardModal").addEventListener("click",()=>{document.getElementById("whiteboardModal").classList.add("hidden")});function A(){const g=document.getElementById("whiteboardCanvas");if(!g)return;const I=g.getContext("2d");let S=!1,L="pen",H="#000000",P=3,q=0,U=0;document.querySelectorAll(".tool-btn").forEach(N=>{N.onclick=()=>{document.querySelectorAll(".tool-btn").forEach(O=>{O.style.background="transparent",O.style.borderColor="var(--border)",O.style.color="inherit",O.classList.remove("active")}),N.style.background="var(--primary)",N.style.borderColor="var(--primary)",N.style.color="white",N.classList.add("active"),L=N.dataset.tool}});const G=document.getElementById("colorPickerCanvas");G&&(G.onchange=N=>{H=N.target.value});const ee=document.getElementById("brushSizeCanvas"),W=document.getElementById("brushSizeLabel");ee&&W&&(ee.oninput=N=>{P=N.target.value,W.textContent=`大小: ${P}`}),g.onmousedown=N=>{S=!0;const O=g.getBoundingClientRect();q=N.clientX-O.left,U=N.clientY-O.top},g.onmousemove=N=>{if(!S)return;const O=g.getBoundingClientRect(),Y=N.clientX-O.left,ae=N.clientY-O.top;I.beginPath(),I.moveTo(q,U),I.lineTo(Y,ae),I.strokeStyle=L==="eraser"?"#ffffff":H,I.lineWidth=P,I.lineCap="round",I.stroke(),q=Y,U=ae},g.onmouseup=()=>{S=!1},g.onmouseleave=()=>{S=!1},document.getElementById("clearCanvasBtn").onclick=()=>{confirm("确定要清空画布吗?")&&I.clearRect(0,0,g.width,g.height)},document.getElementById("saveCanvasBtn").onclick=()=>{const N=g.toDataURL("image/png"),O=document.createElement("a");O.download=`whiteboard-${Date.now()}.png`,O.href=N,O.click(),alert("白板已保存!")},document.getElementById("sendToGroupBtn").onclick=async()=>{try{const N=g.toDataURL("image/png"),O=await fetch(N).then(xe=>xe.blob()),Y=new FormData;Y.append("file",O,`whiteboard-${Date.now()}.png`),Y.append("groupId",p._id),Y.append("description","协作白板作品");const ae=localStorage.getItem("token"),ye=await fetch("http://localhost:8765/api/files/upload",{method:"POST",headers:{Authorization:`Bearer ${ae}`},body:Y});if(ye.ok){const ve=`http://localhost:8765/api/files/${(await ye.json()).file._id}/download?token=${ae}`;a.sendChatMessage(p._id,_.username,`[白板作品]${ve}`),alert("白板作品已发送到群聊!"),document.getElementById("whiteboardModal").classList.add("hidden")}else throw new Error("上传失败")}catch(N){console.error("发送白板作品错误:",N),alert("发送失败: "+N.message)}}}}async function ge(o){if(!p){o.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}o.innerHTML=`
815
+ <div class="message-content" style="${C?"background: linear-gradient(135deg, rgb(99, 102, 241) 0%, rgb(139, 92, 246) 100%); color: white; padding: 16px; border-radius: 12px; box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);":""}">${j}</div>
816
+ `,b.appendChild(L),b.scrollTop=b.scrollHeight}}),e.on("chat_blocked",p=>{if(p.groupId===i._id){const L=document.createElement("div");L.className="notification",L.textContent=p.message||"消息发送失败",b.appendChild(L),b.scrollTop=b.scrollHeight}});const c=()=>{const p=k.value.trim();p&&(e.sendChatMessage(i._id,t.username,p),k.value="")};B.addEventListener("click",c),k.addEventListener("keypress",p=>{p.key==="Enter"&&c()}),document.getElementById("openAIBtn").addEventListener("click",()=>{document.getElementById("aiModal").classList.remove("hidden")}),document.getElementById("closeAIModal").addEventListener("click",()=>{document.getElementById("aiModal").classList.add("hidden")}),document.querySelectorAll(".quick-question-btn").forEach(p=>{p.addEventListener("click",()=>{document.getElementById("aiInputText").value=p.dataset.question,document.getElementById("aiSendBtnModal").click()}),p.addEventListener("mouseenter",L=>{L.target.style.background="var(--primary)",L.target.style.color="white",L.target.style.transform="translateY(-2px)"}),p.addEventListener("mouseleave",L=>{L.target.style.background="var(--bg)",L.target.style.color="inherit",L.target.style.transform="translateY(0)"})});const v=document.getElementById("aiInputText");v.addEventListener("input",()=>{v.style.height="auto",v.style.height=v.scrollHeight+"px"}),v.addEventListener("keydown",p=>{p.key==="Enter"&&!p.shiftKey&&(p.preventDefault(),document.getElementById("aiSendBtnModal").click())}),v.addEventListener("focus",()=>{v.style.borderColor="var(--primary)"}),v.addEventListener("blur",()=>{v.style.borderColor="var(--border)"});const w=document.getElementById("aiSendBtnModal");w.addEventListener("mouseenter",()=>{w.style.transform="scale(1.05)"}),w.addEventListener("mouseleave",()=>{w.style.transform="scale(1)"}),document.getElementById("aiSendBtnModal").addEventListener("click",async()=>{const p=document.getElementById("aiInputText"),L=p.value.trim();if(!L)return;const j=document.getElementById("aiChatMessages"),C=document.createElement("div");C.className="ai-message user",C.style.cssText="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 12px 18px; border-radius: 18px 18px 4px 18px; margin: 10px 0; max-width: 75%; margin-left: auto; text-align: right; box-shadow: 0 2px 8px rgba(102, 126, 234, 0.3); animation: slideInRight 0.3s ease;",C.textContent=L,j.appendChild(C),p.value="";const G=document.createElement("div");G.className="ai-message ai loading",G.style.cssText="background: var(--bg-tertiary); padding: 12px 16px; border-radius: 12px; margin: 10px 0; max-width: 70%;",G.textContent="思考中...",j.appendChild(G),j.scrollTop=j.scrollHeight;try{const q=localStorage.getItem("token"),X=await(await fetch("http://localhost:8765/api/ai/ask",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${q}`},body:JSON.stringify({question:L,groupId:i==null?void 0:i._id})})).json();G.remove();const te=document.createElement("div");te.className="ai-message ai",te.style.cssText="background: var(--bg-secondary); padding: 15px 18px; border-radius: 18px 18px 18px 4px; margin: 10px 0; max-width: 75%; border: 1px solid var(--border); box-shadow: 0 2px 4px rgba(0,0,0,0.05); animation: slideInLeft 0.3s ease; line-height: 1.6;",te.textContent=X.answer||"抱歉,我无法回答这个问题。",j.appendChild(te),j.scrollTop=j.scrollHeight}catch(q){G.remove();const Y=document.createElement("div");Y.className="ai-message ai error",Y.style.cssText="background: var(--danger); color: white; padding: 12px 16px; border-radius: 12px; margin: 10px 0; max-width: 70%;",Y.textContent="抱歉,发生了错误: "+q.message,j.appendChild(Y),j.scrollTop=j.scrollHeight}}),window.showImageModal=p=>{const L=document.getElementById("imagePreviewModal"),j=document.getElementById("previewImage"),C=document.getElementById("downloadImageBtn");j.src=p,L.classList.remove("hidden"),C.onclick=async()=>{try{const q=await(await fetch(p)).blob(),Y=window.URL.createObjectURL(q),X=document.createElement("a");X.href=Y,X.download=`whiteboard-${Date.now()}.png`,document.body.appendChild(X),X.click(),document.body.removeChild(X),window.URL.revokeObjectURL(Y)}catch(G){console.error("下载失败:",G),alert("下载失败,请重试")}}},document.getElementById("closeImagePreview").addEventListener("click",()=>{document.getElementById("imagePreviewModal").classList.add("hidden")}),document.getElementById("imagePreviewModal").addEventListener("click",p=>{p.target.id==="imagePreviewModal"&&document.getElementById("imagePreviewModal").classList.add("hidden")}),document.getElementById("openWhiteboardBtn").addEventListener("click",()=>{document.getElementById("whiteboardModal").classList.remove("hidden"),P()}),document.getElementById("closeWhiteboardModal").addEventListener("click",()=>{document.getElementById("whiteboardModal").classList.add("hidden")});function P(){const p=document.getElementById("whiteboardCanvas");if(!p)return;const L=p.getContext("2d");let j=!1,C="pen",G="#000000",q=3,Y=0,X=0;document.querySelectorAll(".tool-btn").forEach(J=>{J.onclick=()=>{document.querySelectorAll(".tool-btn").forEach(ee=>{ee.style.background="transparent",ee.style.borderColor="var(--border)",ee.style.color="inherit",ee.classList.remove("active")}),J.style.background="var(--primary)",J.style.borderColor="var(--primary)",J.style.color="white",J.classList.add("active"),C=J.dataset.tool}});const te=document.getElementById("colorPickerCanvas");te&&(te.onchange=J=>{G=J.target.value});const ue=document.getElementById("brushSizeCanvas"),ie=document.getElementById("brushSizeLabel");ue&&ie&&(ue.oninput=J=>{q=J.target.value,ie.textContent=`大小: ${q}`}),p.onmousedown=J=>{j=!0;const ee=p.getBoundingClientRect();Y=J.clientX-ee.left,X=J.clientY-ee.top},p.onmousemove=J=>{if(!j)return;const ee=p.getBoundingClientRect(),de=J.clientX-ee.left,ve=J.clientY-ee.top;L.beginPath(),L.moveTo(Y,X),L.lineTo(de,ve),L.strokeStyle=C==="eraser"?"#ffffff":G,L.lineWidth=q,L.lineCap="round",L.stroke(),Y=de,X=ve},p.onmouseup=()=>{j=!1},p.onmouseleave=()=>{j=!1},document.getElementById("clearCanvasBtn").onclick=()=>{confirm("确定要清空画布吗?")&&L.clearRect(0,0,p.width,p.height)},document.getElementById("saveCanvasBtn").onclick=()=>{const J=p.toDataURL("image/png"),ee=document.createElement("a");ee.download=`whiteboard-${Date.now()}.png`,ee.href=J,ee.click(),alert("白板已保存!")},document.getElementById("sendToGroupBtn").onclick=async()=>{try{const J=p.toDataURL("image/png"),ee=await fetch(J).then(Ze=>Ze.blob()),de=new FormData;de.append("file",ee,`whiteboard-${Date.now()}.png`),de.append("groupId",i._id),de.append("description","协作白板作品");const ve=localStorage.getItem("token"),Xe=await fetch("http://localhost:8765/api/files/upload",{method:"POST",headers:{Authorization:`Bearer ${ve}`},body:de});if(Xe.ok){const Mt=`http://localhost:8765/api/files/${(await Xe.json()).file._id}/download?token=${ve}`;e.sendChatMessage(i._id,t.username,`[白板作品]${Mt}`),alert("白板作品已发送到群聊!"),document.getElementById("whiteboardModal").classList.add("hidden")}else throw new Error("上传失败")}catch(J){console.error("发送白板作品错误:",J),alert("发送失败: "+J.message)}}}}async function V(h){if(!i){h.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}h.innerHTML=`
814
817
  <div class="view-header">
815
- <h2>随机点名 - ${p.name}</h2>
818
+ <h2>随机点名 - ${i.name}</h2>
816
819
  </div>
817
820
  <div class="call-panel">
818
821
  <div class="call-controls">
@@ -822,17 +825,17 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
822
825
  </div>
823
826
  <div id="callResult" class="call-result"></div>
824
827
  </div>
825
- `,document.getElementById("randomCallBtn").addEventListener("click",async()=>{const i=parseInt(document.getElementById("callCount").value);try{const n=await d.randomCall(p._id,i),s=document.getElementById("callResult");if(s.innerHTML=`
828
+ `,document.getElementById("randomCallBtn").addEventListener("click",async()=>{const m=parseInt(document.getElementById("callCount").value);try{const u=await a.randomCall(i._id,m),b=document.getElementById("callResult");if(b.innerHTML=`
826
829
  <h3>点名结果:</h3>
827
830
  <div class="called-members">
828
- ${n.calledMembers.map(x=>`
831
+ ${u.calledMembers.map(k=>`
829
832
  <div class="member-card">
830
- <div class="avatar">${x.username[0].toUpperCase()}</div>
831
- <div class="member-name">${x.username}</div>
833
+ <div class="avatar">${k.username[0].toUpperCase()}</div>
834
+ <div class="member-name">${k.username}</div>
832
835
  </div>
833
836
  `).join("")}
834
837
  </div>
835
- `,Array.isArray(n.calledMembers)&&n.calledMembers.length>0&&a&&p&&_){const x=n.calledMembers.map(r=>r.username).join("、"),B=`🎲 本次随机点名(${n.calledMembers.length} 人):${x}`;try{a.sendChatMessage(p._id,_.username,B)}catch(r){console.error("发送点名结果到群聊失败:",r)}}}catch(n){alert("点名失败: "+n.message)}})}async function ie(o){o.innerHTML=`
838
+ `,Array.isArray(u.calledMembers)&&u.calledMembers.length>0&&e&&i&&t){const k=u.calledMembers.map(y=>y.username).join("、"),B=`🎲 本次随机点名(${u.calledMembers.length} 人):${k}`;try{e.sendChatMessage(i._id,t.username,B)}catch(y){console.error("发送点名结果到群聊失败:",y)}}}catch(u){alert("点名失败: "+u.message)}})}async function Q(h){h.innerHTML=`
836
839
  <div class="view-header">
837
840
  <h2>操作记录</h2>
838
841
  <div style="display: flex; gap: 10px;">
@@ -896,7 +899,7 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
896
899
  </div>
897
900
  </div>
898
901
  </div>
899
- `;let i=1,n={};try{const e=await d.getGroups(),l=document.getElementById("auditGroupFilter");e.groups.forEach(c=>{const h=document.createElement("option");h.value=c._id,h.textContent=c.name,l.appendChild(h)})}catch(e){console.error("加载群组列表失败:",e)}async function s(e=1,l={}){try{const c=document.getElementById("auditLogs");c.innerHTML='<div class="loading">加载中...</div>';const h={page:e,limit:20},E=await d.getAuditLogs(l,h);if(E.logs.length===0){c.innerHTML='<div class="empty-state">暂无操作记录</div>',document.getElementById("auditPagination").style.display="none";return}c.innerHTML=`
902
+ `;let m=1,u={};try{const r=await a.getGroups(),d=document.getElementById("auditGroupFilter");r.groups.forEach(s=>{const c=document.createElement("option");c.value=s._id,c.textContent=s.name,d.appendChild(c)})}catch(r){console.error("加载群组列表失败:",r)}async function b(r=1,d={}){try{const s=document.getElementById("auditLogs");s.innerHTML='<div class="loading">加载中...</div>';const c={page:r,limit:20},v=await a.getAuditLogs(d,c);if(v.logs.length===0){s.innerHTML='<div class="empty-state">暂无操作记录</div>',document.getElementById("auditPagination").style.display="none";return}s.innerHTML=`
900
903
  <div class="audit-table">
901
904
  <div class="audit-header">
902
905
  <div>时间</div>
@@ -905,26 +908,26 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
905
908
  <div>资源</div>
906
909
  <div>详情</div>
907
910
  </div>
908
- ${E.logs.map(g=>{var I,S,L,H,P;return`
909
- <div class="audit-row" onclick="showAuditDetail('${g._id}')">
910
- <div class="audit-time">${new Date(g.createdAt).toLocaleString()}</div>
911
+ ${v.logs.map(p=>{var L,j,C,G,q;return`
912
+ <div class="audit-row" onclick="showAuditDetail('${p._id}')">
913
+ <div class="audit-time">${new Date(p.createdAt).toLocaleString()}</div>
911
914
  <div class="audit-user">
912
- <div class="avatar">${((L=(S=(I=g.user)==null?void 0:I.username)==null?void 0:S[0])==null?void 0:L.toUpperCase())||"?"}</div>
913
- <span>${((H=g.user)==null?void 0:H.username)||"未知用户"}</span>
915
+ <div class="avatar">${((C=(j=(L=p.user)==null?void 0:L.username)==null?void 0:j[0])==null?void 0:C.toUpperCase())||"?"}</div>
916
+ <span>${((G=p.user)==null?void 0:G.username)||"未知用户"}</span>
914
917
  </div>
915
918
  <div class="audit-action">
916
- <span class="action-badge action-${g.action}">${f(g.action)}</span>
919
+ <span class="action-badge action-${p.action}">${T(p.action)}</span>
917
920
  </div>
918
- <div class="audit-resource">${g.resourceTitle||g.resourceId}</div>
919
- <div class="audit-description">${((P=g.details)==null?void 0:P.description)||"-"}</div>
921
+ <div class="audit-resource">${p.resourceTitle||p.resourceId}</div>
922
+ <div class="audit-description">${((q=p.details)==null?void 0:q.description)||"-"}</div>
920
923
  </div>
921
924
  `}).join("")}
922
925
  </div>
923
- `;const k=document.getElementById("auditPagination"),A=document.getElementById("pageInfo");A.textContent=`第 ${E.pagination.page} 页,共 ${E.pagination.pages} 页`,document.getElementById("prevPage").disabled=E.pagination.page<=1,document.getElementById("nextPage").disabled=E.pagination.page>=E.pagination.pages,k.style.display=E.pagination.pages>1?"flex":"none"}catch(c){console.error("加载审计日志失败:",c),document.getElementById("auditLogs").innerHTML='<div class="error-state">加载失败: '+c.message+"</div>"}}async function x(){try{const e=new Date,l=new Date(e.getTime()-7*24*60*60*1e3),c=await d.getAuditSummary({startDate:e.toISOString().split("T")[0],endDate:e.toISOString().split("T")[0]}),h=await d.getAuditSummary({startDate:l.toISOString().split("T")[0],endDate:e.toISOString().split("T")[0]});document.getElementById("todayCount").textContent=c.summary.totalLogs,document.getElementById("weekCount").textContent=h.summary.totalLogs,document.getElementById("activeUsers").textContent=h.summary.topUsers.length}catch(e){console.error("加载统计信息失败:",e)}}function B(e){var k,A,g,I,S,L;const l=((k=e.user)==null?void 0:k.username)||"未知用户",c=f(e.action),h=e.resourceTitle||e.resourceId;let E='<strong style="color: #6366f1;">'+l+"</strong> ";switch(e.action){case"document_create":E+='创建了文档 <strong>"'+h+'"</strong>';break;case"document_update":E+='更新了文档 <strong>"'+h+'"</strong>',(A=e.details)!=null&&A.field&&(E+=" 的 <strong>"+r(e.details.field)+"</strong>");break;case"document_delete":E+='删除了文档 <strong>"'+h+'"</strong>';break;case"content_edit":if(E+='编辑了文档 <strong>"'+h+'"</strong> 的内容',e.changes){const H=((g=e.changes.insertions)==null?void 0:g.reduce((q,U)=>q+U.length,0))||0,P=((I=e.changes.deletions)==null?void 0:I.reduce((q,U)=>q+U.length,0))||0;(H>0||P>0)&&(E+=' (<span style="color: #10b981;">+'+H+'</span> / <span style="color: #ef4444;">-'+P+"</span> 字符)")}break;case"title_edit":E+='修改了文档 <strong>"'+h+'"</strong> 的标题',(S=e.details)!=null&&S.oldValue&&((L=e.details)!=null&&L.newValue)&&(E+=' 从 <strong>"'+e.details.oldValue+'"</strong> 改为 <strong>"'+e.details.newValue+'"</strong>');break;case"document_permission_change":E+='修改了文档 <strong>"'+h+'"</strong> 的权限设置';break;default:E+="执行了 <strong>"+c+"</strong> 操作"}return E}function r(e){return{title:"标题",content:"内容",permissions:"权限",status:"状态",tags:"标签",category:"分类",description:"描述"}[e]||e}function t(e){if(e==null)return'<span style="color: var(--text-tertiary); font-style: italic;">空</span>';if(typeof e=="object")return JSON.stringify(e,null,2);const l=String(e);return l.length>500?l.substring(0,500)+'... <span style="color: var(--text-tertiary); font-style: italic;">(内容过长,已截断)</span>':l.replace(/</g,"&lt;").replace(/>/g,"&gt;")}window.showAuditDetail=async e=>{var l,c,h,E,k,A;try{const g=localStorage.getItem("token"),L=(await(await fetch(`http://localhost:8765/api/audit/${e}`,{headers:{Authorization:`Bearer ${g}`}})).json()).log,H=document.getElementById("auditDetailModal"),P=document.getElementById("auditDetailContent"),q=ee=>({create:"#10b981",update:"#f59e0b",delete:"#ef4444",login:"#6366f1",logout:"#8b5cf6"})[ee]||"#6366f1";P.innerHTML=`
926
+ `;const w=document.getElementById("auditPagination"),P=document.getElementById("pageInfo");P.textContent=`第 ${v.pagination.page} 页,共 ${v.pagination.pages} 页`,document.getElementById("prevPage").disabled=v.pagination.page<=1,document.getElementById("nextPage").disabled=v.pagination.page>=v.pagination.pages,w.style.display=v.pagination.pages>1?"flex":"none"}catch(s){console.error("加载审计日志失败:",s),document.getElementById("auditLogs").innerHTML='<div class="error-state">加载失败: '+s.message+"</div>"}}async function k(){try{const r=new Date,d=new Date(r.getTime()-7*24*60*60*1e3),s=await a.getAuditSummary({startDate:r.toISOString().split("T")[0],endDate:r.toISOString().split("T")[0]}),c=await a.getAuditSummary({startDate:d.toISOString().split("T")[0],endDate:r.toISOString().split("T")[0]});document.getElementById("todayCount").textContent=s.summary.totalLogs,document.getElementById("weekCount").textContent=c.summary.totalLogs,document.getElementById("activeUsers").textContent=c.summary.topUsers.length}catch(r){console.error("加载统计信息失败:",r)}}function B(r){var w,P,p,L,j,C;const d=((w=r.user)==null?void 0:w.username)||"未知用户",s=T(r.action),c=r.resourceTitle||r.resourceId;let v='<strong style="color: #6366f1;">'+d+"</strong> ";switch(r.action){case"document_create":v+='创建了文档 <strong>"'+c+'"</strong>';break;case"document_update":v+='更新了文档 <strong>"'+c+'"</strong>',(P=r.details)!=null&&P.field&&(v+=" 的 <strong>"+y(r.details.field)+"</strong>");break;case"document_delete":v+='删除了文档 <strong>"'+c+'"</strong>';break;case"content_edit":if(v+='编辑了文档 <strong>"'+c+'"</strong> 的内容',r.changes){const G=((p=r.changes.insertions)==null?void 0:p.reduce((Y,X)=>Y+X.length,0))||0,q=((L=r.changes.deletions)==null?void 0:L.reduce((Y,X)=>Y+X.length,0))||0;(G>0||q>0)&&(v+=' (<span style="color: #10b981;">+'+G+'</span> / <span style="color: #ef4444;">-'+q+"</span> 字符)")}break;case"title_edit":v+='修改了文档 <strong>"'+c+'"</strong> 的标题',(j=r.details)!=null&&j.oldValue&&((C=r.details)!=null&&C.newValue)&&(v+=' 从 <strong>"'+r.details.oldValue+'"</strong> 改为 <strong>"'+r.details.newValue+'"</strong>');break;case"document_permission_change":v+='修改了文档 <strong>"'+c+'"</strong> 的权限设置';break;default:v+="执行了 <strong>"+s+"</strong> 操作"}return v}function y(r){return{title:"标题",content:"内容",permissions:"权限",status:"状态",tags:"标签",category:"分类",description:"描述"}[r]||r}function g(r){if(r==null)return'<span style="color: var(--text-tertiary); font-style: italic;">空</span>';if(typeof r=="object")return JSON.stringify(r,null,2);const d=String(r);return d.length>500?d.substring(0,500)+'... <span style="color: var(--text-tertiary); font-style: italic;">(内容过长,已截断)</span>':d.replace(/</g,"&lt;").replace(/>/g,"&gt;")}window.showAuditDetail=async r=>{var d,s,c,v,w,P;try{const p=localStorage.getItem("token"),C=(await(await fetch(`http://localhost:8765/api/audit/${r}`,{headers:{Authorization:`Bearer ${p}`}})).json()).log,G=document.getElementById("auditDetailModal"),q=document.getElementById("auditDetailContent"),Y=ue=>({create:"#10b981",update:"#f59e0b",delete:"#ef4444",login:"#6366f1",logout:"#8b5cf6"})[ue]||"#6366f1";q.innerHTML=`
924
927
  <div class="audit-detail" style="padding: 0; background: linear-gradient(135deg, rgba(99, 102, 241, 0.03) 0%, rgba(168, 85, 247, 0.03) 100%);">
925
928
 
926
929
  <!-- 操作信息 -->
927
- <div class="detail-section" style="margin-bottom: 12px; padding: 12px; background: var(--bg-secondary); border-radius: 8px; border-left: 4px solid ${q(L.action)}; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
930
+ <div class="detail-section" style="margin-bottom: 12px; padding: 12px; background: var(--bg-secondary); border-radius: 8px; border-left: 4px solid ${Y(C.action)}; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
928
931
  <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px;">
929
932
  <span style="font-size: 24px;">📋</span>
930
933
  <h4 style="margin: 0; color: var(--text-primary); font-size: 14px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">操作信息</h4>
@@ -932,14 +935,14 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
932
935
  <div style="display: grid; grid-template-columns: 100px 1fr; gap: 10px; font-size: 13px;">
933
936
  <span style="color: var(--text-tertiary); font-weight: 500;">操作类型:</span>
934
937
  <span style="display: inline-flex; align-items: center; gap: 8px;">
935
- <span style="display: inline-block; padding: 4px 10px; background: ${q(L.action)}; color: white; border-radius: 6px; font-weight: 600; font-size: 12px; box-shadow: 0 2px 6px rgba(0,0,0,0.15);">${f(L.action)}</span>
938
+ <span style="display: inline-block; padding: 4px 10px; background: ${Y(C.action)}; color: white; border-radius: 6px; font-weight: 600; font-size: 12px; box-shadow: 0 2px 6px rgba(0,0,0,0.15);">${T(C.action)}</span>
936
939
  </span>
937
940
  <span style="color: var(--text-tertiary); font-weight: 500;">操作时间:</span>
938
- <span style="font-weight: 600; color: var(--text-primary);">${new Date(L.createdAt).toLocaleString("zh-CN",{year:"numeric",month:"long",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}</span>
941
+ <span style="font-weight: 600; color: var(--text-primary);">${new Date(C.createdAt).toLocaleString("zh-CN",{year:"numeric",month:"long",day:"numeric",hour:"2-digit",minute:"2-digit",second:"2-digit"})}</span>
939
942
  <span style="color: var(--text-tertiary); font-weight: 500;">操作用户:</span>
940
943
  <span style="display: flex; align-items: center; gap: 10px;">
941
- <div class="avatar" style="width: 32px; height: 32px; font-size: 14px; background: linear-gradient(135deg, #6366f1 0%, #a855f7 100%);">${((h=(c=(l=L.user)==null?void 0:l.username)==null?void 0:c[0])==null?void 0:h.toUpperCase())||"?"}</div>
942
- <span style="font-weight: 600; color: var(--text-primary);">${((E=L.user)==null?void 0:E.username)||"未知用户"}</span>
944
+ <div class="avatar" style="width: 32px; height: 32px; font-size: 14px; background: linear-gradient(135deg, #6366f1 0%, #a855f7 100%);">${((c=(s=(d=C.user)==null?void 0:d.username)==null?void 0:s[0])==null?void 0:c.toUpperCase())||"?"}</div>
945
+ <span style="font-weight: 600; color: var(--text-primary);">${((v=C.user)==null?void 0:v.username)||"未知用户"}</span>
943
946
  </span>
944
947
  </div>
945
948
  </div>
@@ -952,15 +955,15 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
952
955
  </div>
953
956
  <div style="display: grid; grid-template-columns: 100px 1fr; gap: 10px; font-size: 13px;">
954
957
  <span style="color: var(--text-tertiary); font-weight: 500;">资源类型:</span>
955
- <span style="font-weight: 600; color: var(--text-primary);">${L.resourceType||"未知"}</span>
958
+ <span style="font-weight: 600; color: var(--text-primary);">${C.resourceType||"未知"}</span>
956
959
  <span style="color: var(--text-tertiary); font-weight: 500;">资源ID:</span>
957
- <span style="font-family: 'Courier New', monospace; font-size: 13px; padding: 6px 12px; background: var(--bg); border-radius: 6px; color: var(--text-primary); border: 1px solid var(--border);">${L.resourceId}</span>
960
+ <span style="font-family: 'Courier New', monospace; font-size: 13px; padding: 6px 12px; background: var(--bg); border-radius: 6px; color: var(--text-primary); border: 1px solid var(--border);">${C.resourceId}</span>
958
961
  <span style="color: var(--text-tertiary); font-weight: 500;">资源标题:</span>
959
- <span style="font-weight: 600; color: var(--text-primary);">${L.resourceTitle||'<span style="color: var(--text-tertiary); font-style: italic;">无</span>'}</span>
962
+ <span style="font-weight: 600; color: var(--text-primary);">${C.resourceTitle||'<span style="color: var(--text-tertiary); font-style: italic;">无</span>'}</span>
960
963
  </div>
961
964
  </div>
962
965
 
963
- ${L.details?`
966
+ ${C.details?`
964
967
  <!-- 详细信息 -->
965
968
  <div class="detail-section" style="margin-bottom: 12px; padding: 12px; background: var(--bg-secondary); border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.05);">
966
969
  <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 10px;">
@@ -1000,13 +1003,13 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
1000
1003
  </div>
1001
1004
  <div style="display: grid; grid-template-columns: 100px 1fr; gap: 10px; font-size: 13px;">
1002
1005
  <span style="color: var(--text-tertiary); font-weight: 500;">IP地址:</span>
1003
- <span style="font-family: 'Courier New', monospace; font-weight: 600; color: var(--text-primary);">${L.ipAddress||'<span style="color: var(--text-tertiary); font-style: italic;">未记录</span>'}</span>
1006
+ <span style="font-family: 'Courier New', monospace; font-weight: 600; color: var(--text-primary);">${C.ipAddress||'<span style="color: var(--text-tertiary); font-style: italic;">未记录</span>'}</span>
1004
1007
  <span style="color: var(--text-tertiary); font-weight: 500;">用户代理:</span>
1005
- <span style="font-size: 13px; word-break: break-all; color: var(--text-secondary); line-height: 1.6;">${L.userAgent||'<span style="color: var(--text-tertiary); font-style: italic;">未记录</span>'}</span>
1008
+ <span style="font-size: 13px; word-break: break-all; color: var(--text-secondary); line-height: 1.6;">${C.userAgent||'<span style="color: var(--text-tertiary); font-style: italic;">未记录</span>'}</span>
1006
1009
  </div>
1007
1010
  </div>
1008
1011
  </div>
1009
- `,document.getElementById("auditDescriptionText").innerHTML=B(L);const U=document.getElementById("auditDetailsContent");let G="";if(L.details&&(L.details.field&&(G+='<span style="color: var(--text-tertiary); font-weight: 500;">修改字段:</span>',G+='<span style="font-weight: 600; color: var(--text-primary);">'+r(L.details.field)+"</span>"),L.details.oldValue!==void 0&&L.details.oldValue!==null&&(G+='<span style="color: var(--text-tertiary); font-weight: 500;">原始值:</span>',G+=`<div style="padding: 8px 12px; background: rgba(239, 68, 68, 0.1); border-radius: 6px; border-left: 3px solid #ef4444; font-family: 'Courier New', monospace; font-size: 13px; word-break: break-all; max-height: 200px; overflow-y: auto;">`+t(L.details.oldValue)+"</div>"),L.details.newValue!==void 0&&L.details.newValue!==null&&(G+='<span style="color: var(--text-tertiary); font-weight: 500;">新值:</span>',G+=`<div style="padding: 8px 12px; background: rgba(16, 185, 129, 0.1); border-radius: 6px; border-left: 3px solid #10b981; font-family: 'Courier New', monospace; font-size: 13px; word-break: break-all; max-height: 200px; overflow-y: auto;">`+t(L.details.newValue)+"</div>")),G?(U.innerHTML=G,document.getElementById("auditDetailsSection").style.display="block"):document.getElementById("auditDetailsSection").style.display="none",L.changes&&(((k=L.changes.insertions)==null?void 0:k.length)>0||((A=L.changes.deletions)==null?void 0:A.length)>0)){const ee=document.getElementById("auditChangesContent");let W="";if(L.changes.insertions&&L.changes.insertions.length>0){const N=L.changes.insertions.reduce((O,Y)=>O+Y.length,0);W+='<div style="flex: 1; padding: 12px; background: rgba(16, 185, 129, 0.1); border-radius: 8px; border-left: 3px solid #10b981;">',W+='<div style="font-weight: 600; color: #10b981; margin-bottom: 4px;">✅ 新增内容</div>',W+='<div style="color: var(--text-secondary);">共 '+N+" 个字符</div>",W+="</div>"}if(L.changes.deletions&&L.changes.deletions.length>0){const N=L.changes.deletions.reduce((O,Y)=>O+Y.length,0);W+='<div style="flex: 1; padding: 12px; background: rgba(239, 68, 68, 0.1); border-radius: 8px; border-left: 3px solid #ef4444;">',W+='<div style="font-weight: 600; color: #ef4444; margin-bottom: 4px;">❌ 删除内容</div>',W+='<div style="color: var(--text-secondary);">共 '+N+" 个字符</div>",W+="</div>"}ee.innerHTML=W,document.getElementById("auditChangesSection").style.display="block"}else document.getElementById("auditChangesSection").style.display="none";H.classList.remove("hidden")}catch(g){alert("加载详情失败: "+g.message)}},document.getElementById("applyFilters").addEventListener("click",()=>{n={groupId:document.getElementById("auditGroupFilter").value,action:document.getElementById("auditActionFilter").value,startDate:document.getElementById("startDate").value,endDate:document.getElementById("endDate").value},Object.keys(n).forEach(e=>{n[e]||delete n[e]}),i=1,s(i,n)}),document.getElementById("prevPage").addEventListener("click",()=>{i>1&&(i--,s(i,n))}),document.getElementById("nextPage").addEventListener("click",()=>{i++,s(i,n)}),document.getElementById("exportLogs").addEventListener("click",()=>{alert("导出功能开发中...")}),document.getElementById("clearAuditLogs").addEventListener("click",async()=>{const l=Object.keys(n||{}).length>0?"将清除当前筛选条件下的所有操作记录,确认继续?":"⚠️ 将清除全部操作记录(不可恢复),确认继续?";if(confirm(l))try{const c=await d.clearAuditLogs(n||{});alert(`已清除 ${c.deletedCount||0} 条操作记录`),i=1,await x(),await s(i,n)}catch(c){alert("清除失败: "+c.message)}}),document.getElementById("closeAuditDetail").addEventListener("click",()=>{document.getElementById("auditDetailModal").classList.add("hidden")}),x(),s()}async function re(o){o.innerHTML=`
1012
+ `,document.getElementById("auditDescriptionText").innerHTML=B(C);const X=document.getElementById("auditDetailsContent");let te="";if(C.details&&(C.details.field&&(te+='<span style="color: var(--text-tertiary); font-weight: 500;">修改字段:</span>',te+='<span style="font-weight: 600; color: var(--text-primary);">'+y(C.details.field)+"</span>"),C.details.oldValue!==void 0&&C.details.oldValue!==null&&(te+='<span style="color: var(--text-tertiary); font-weight: 500;">原始值:</span>',te+=`<div style="padding: 8px 12px; background: rgba(239, 68, 68, 0.1); border-radius: 6px; border-left: 3px solid #ef4444; font-family: 'Courier New', monospace; font-size: 13px; word-break: break-all; max-height: 200px; overflow-y: auto;">`+g(C.details.oldValue)+"</div>"),C.details.newValue!==void 0&&C.details.newValue!==null&&(te+='<span style="color: var(--text-tertiary); font-weight: 500;">新值:</span>',te+=`<div style="padding: 8px 12px; background: rgba(16, 185, 129, 0.1); border-radius: 6px; border-left: 3px solid #10b981; font-family: 'Courier New', monospace; font-size: 13px; word-break: break-all; max-height: 200px; overflow-y: auto;">`+g(C.details.newValue)+"</div>")),te?(X.innerHTML=te,document.getElementById("auditDetailsSection").style.display="block"):document.getElementById("auditDetailsSection").style.display="none",C.changes&&(((w=C.changes.insertions)==null?void 0:w.length)>0||((P=C.changes.deletions)==null?void 0:P.length)>0)){const ue=document.getElementById("auditChangesContent");let ie="";if(C.changes.insertions&&C.changes.insertions.length>0){const J=C.changes.insertions.reduce((ee,de)=>ee+de.length,0);ie+='<div style="flex: 1; padding: 12px; background: rgba(16, 185, 129, 0.1); border-radius: 8px; border-left: 3px solid #10b981;">',ie+='<div style="font-weight: 600; color: #10b981; margin-bottom: 4px;">✅ 新增内容</div>',ie+='<div style="color: var(--text-secondary);">共 '+J+" 个字符</div>",ie+="</div>"}if(C.changes.deletions&&C.changes.deletions.length>0){const J=C.changes.deletions.reduce((ee,de)=>ee+de.length,0);ie+='<div style="flex: 1; padding: 12px; background: rgba(239, 68, 68, 0.1); border-radius: 8px; border-left: 3px solid #ef4444;">',ie+='<div style="font-weight: 600; color: #ef4444; margin-bottom: 4px;">❌ 删除内容</div>',ie+='<div style="color: var(--text-secondary);">共 '+J+" 个字符</div>",ie+="</div>"}ue.innerHTML=ie,document.getElementById("auditChangesSection").style.display="block"}else document.getElementById("auditChangesSection").style.display="none";G.classList.remove("hidden")}catch(p){alert("加载详情失败: "+p.message)}},document.getElementById("applyFilters").addEventListener("click",()=>{u={groupId:document.getElementById("auditGroupFilter").value,action:document.getElementById("auditActionFilter").value,startDate:document.getElementById("startDate").value,endDate:document.getElementById("endDate").value},Object.keys(u).forEach(r=>{u[r]||delete u[r]}),m=1,b(m,u)}),document.getElementById("prevPage").addEventListener("click",()=>{m>1&&(m--,b(m,u))}),document.getElementById("nextPage").addEventListener("click",()=>{m++,b(m,u)}),document.getElementById("exportLogs").addEventListener("click",()=>{alert("导出功能开发中...")}),document.getElementById("clearAuditLogs").addEventListener("click",async()=>{const d=Object.keys(u||{}).length>0?"将清除当前筛选条件下的所有操作记录,确认继续?":"⚠️ 将清除全部操作记录(不可恢复),确认继续?";if(confirm(d))try{const s=await a.clearAuditLogs(u||{});alert(`已清除 ${s.deletedCount||0} 条操作记录`),m=1,await k(),await b(m,u)}catch(s){alert("清除失败: "+s.message)}}),document.getElementById("closeAuditDetail").addEventListener("click",()=>{document.getElementById("auditDetailModal").classList.add("hidden")}),k(),b()}async function ne(h){h.innerHTML=`
1010
1013
  <div class="view-header">
1011
1014
  <h2>🔍 搜索</h2>
1012
1015
  </div>
@@ -1028,20 +1031,20 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
1028
1031
  </div>
1029
1032
  <div class="search-results" id="searchResults"></div>
1030
1033
  </div>
1031
- `;const i=document.getElementById("searchInput"),n=document.getElementById("searchBtn"),s=document.getElementById("searchResults"),x=async()=>{const B=i.value.trim();if(!B){s.innerHTML='<div class="empty-state">请输入搜索关键词</div>';return}const r={messages:document.getElementById("filterMessages").checked,documents:document.getElementById("filterDocuments").checked,tasks:document.getElementById("filterTasks").checked};s.innerHTML='<div class="loading">搜索中...</div>';try{const t=[];if(r.messages&&p)try{const e=await d.getGroupMessages(p._id);e.messages&&e.messages.filter(c=>c.content.toLowerCase().includes(B.toLowerCase())).forEach(c=>{t.push({type:"message",title:`消息 - ${c.username}`,content:c.content,time:c.timestamp,group:p.name})})}catch(e){console.error("搜索消息失败:",e)}if(r.documents)try{if(p){const e=await d.getDocuments(p._id);e.documents&&e.documents.filter(c=>c.title.toLowerCase().includes(B.toLowerCase())||c.content.toLowerCase().includes(B.toLowerCase())).forEach(c=>{t.push({type:"document",title:c.title,content:c.content.substring(0,200),time:c.updatedAt,id:c._id,group:p.name})})}}catch(e){console.error("搜索文档失败:",e)}if(r.tasks&&p)try{const e=await d.getTasks(p._id);e.tasks&&e.tasks.filter(c=>c.title.toLowerCase().includes(B.toLowerCase())||c.description&&c.description.toLowerCase().includes(B.toLowerCase())).forEach(c=>{t.push({type:"task",title:c.title,content:c.description||"",time:c.updatedAt,id:c._id,status:c.status,group:p.name})})}catch(e){console.error("搜索任务失败:",e)}t.length===0?s.innerHTML='<div class="empty-state">未找到相关结果</div>':s.innerHTML=t.map(e=>`
1034
+ `;const m=document.getElementById("searchInput"),u=document.getElementById("searchBtn"),b=document.getElementById("searchResults"),k=async()=>{const B=m.value.trim();if(!B){b.innerHTML='<div class="empty-state">请输入搜索关键词</div>';return}const y={messages:document.getElementById("filterMessages").checked,documents:document.getElementById("filterDocuments").checked,tasks:document.getElementById("filterTasks").checked};b.innerHTML='<div class="loading">搜索中...</div>';try{const g=[];if(y.messages&&i)try{const r=await a.getGroupMessages(i._id);r.messages&&r.messages.filter(s=>s.content.toLowerCase().includes(B.toLowerCase())).forEach(s=>{g.push({type:"message",title:`消息 - ${s.username}`,content:s.content,time:s.timestamp,group:i.name})})}catch(r){console.error("搜索消息失败:",r)}if(y.documents)try{if(i){const r=await a.getDocuments(i._id);r.documents&&r.documents.filter(s=>s.title.toLowerCase().includes(B.toLowerCase())||s.content.toLowerCase().includes(B.toLowerCase())).forEach(s=>{g.push({type:"document",title:s.title,content:s.content.substring(0,200),time:s.updatedAt,id:s._id,group:i.name})})}}catch(r){console.error("搜索文档失败:",r)}if(y.tasks&&i)try{const r=await a.getTasks(i._id);r.tasks&&r.tasks.filter(s=>s.title.toLowerCase().includes(B.toLowerCase())||s.description&&s.description.toLowerCase().includes(B.toLowerCase())).forEach(s=>{g.push({type:"task",title:s.title,content:s.description||"",time:s.updatedAt,id:s._id,status:s.status,group:i.name})})}catch(r){console.error("搜索任务失败:",r)}g.length===0?b.innerHTML='<div class="empty-state">未找到相关结果</div>':b.innerHTML=g.map(r=>`
1032
1035
  <div class="search-result-item">
1033
1036
  <div class="result-header">
1034
- <span class="result-type">${{message:"💬",document:"📄",task:"📋"}[e.type]} ${e.type==="message"?"消息":e.type==="document"?"文档":"任务"}</span>
1035
- <span class="result-time">${new Date(e.time).toLocaleString()}</span>
1037
+ <span class="result-type">${{message:"💬",document:"📄",task:"📋"}[r.type]} ${r.type==="message"?"消息":r.type==="document"?"文档":"任务"}</span>
1038
+ <span class="result-time">${new Date(r.time).toLocaleString()}</span>
1036
1039
  </div>
1037
- <h4>${Z(e.title,B)}</h4>
1038
- <p>${Z(e.content,B)}</p>
1039
- ${e.group?`<span class="result-group">群组: ${e.group}</span>`:""}
1040
- ${e.status?`<span class="result-status">状态: ${b(e.status)}</span>`:""}
1040
+ <h4>${re(r.title,B)}</h4>
1041
+ <p>${re(r.content,B)}</p>
1042
+ ${r.group?`<span class="result-group">群组: ${r.group}</span>`:""}
1043
+ ${r.status?`<span class="result-status">状态: ${E(r.status)}</span>`:""}
1041
1044
  </div>
1042
- `).join("")}catch(t){s.innerHTML=`<div class="empty-state">搜索失败: ${t.message}</div>`}};n.addEventListener("click",x),i.addEventListener("keypress",B=>{B.key==="Enter"&&x()})}function Z(o,i){if(!i)return o;const n=new RegExp(`(${i})`,"gi");return o.replace(n,"<mark>$1</mark>")}function f(o){return{document_create:"创建文档",document_update:"更新文档",document_delete:"删除文档",content_edit:"编辑内容",title_edit:"修改标题",document_permission_change:"权限修改"}[o]||o}function b(o){return{pending:"待处理",in_progress:"进行中",completed:"已完成",terminated:"已终止"}[o]||o}async function T(o){var i;if(!p){o.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const n=localStorage.getItem("token"),s=await fetch(`http://localhost:8765/api/knowledge/group/${p._id}`,{headers:{Authorization:`Bearer ${n}`}});if(!s.ok)throw new Error(`HTTP ${s.status}: ${s.statusText}`);const x=await s.json();console.log("知识库数据:",x);const B=((i=x.data)==null?void 0:i.knowledgeList)||[];console.log("知识库条目数量:",B.length),o.innerHTML=`
1045
+ `).join("")}catch(g){b.innerHTML=`<div class="empty-state">搜索失败: ${g.message}</div>`}};u.addEventListener("click",k),m.addEventListener("keypress",B=>{B.key==="Enter"&&k()})}function re(h,m){if(!m)return h;const u=new RegExp(`(${m})`,"gi");return h.replace(u,"<mark>$1</mark>")}function T(h){return{document_create:"创建文档",document_update:"更新文档",document_delete:"删除文档",content_edit:"编辑内容",title_edit:"修改标题",document_permission_change:"权限修改"}[h]||h}function E(h){return{pending:"待处理",in_progress:"进行中",completed:"已完成",terminated:"已终止"}[h]||h}async function F(h){var m;if(!i){h.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const u=localStorage.getItem("token"),b=await fetch(`http://localhost:8765/api/knowledge/group/${i._id}`,{headers:{Authorization:`Bearer ${u}`}});if(!b.ok)throw new Error(`HTTP ${b.status}: ${b.statusText}`);const k=await b.json();console.log("知识库数据:",k);const B=((m=k.data)==null?void 0:m.knowledgeList)||[];console.log("知识库条目数量:",B.length),h.innerHTML=`
1043
1046
  <div class="view-header">
1044
- <h2>📚 知识库管理 - ${p.name}</h2>
1047
+ <h2>📚 知识库管理 - ${i.name}</h2>
1045
1048
  <button class="btn-primary" id="createKnowledgeBtn">➕ 创建知识条目</button>
1046
1049
  </div>
1047
1050
  <div class="knowledge-grid" id="knowledgeList" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 20px; padding: 20px;"></div>
@@ -1078,26 +1081,26 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
1078
1081
  </form>
1079
1082
  </div>
1080
1083
  </div>
1081
- `;const r=document.getElementById("knowledgeList");B.length===0?r.innerHTML='<div class="empty-state" style="grid-column: 1/-1;">暂无知识条目</div>':(B.forEach(t=>{var l;const e=document.createElement("div");e.className="knowledge-card",e.style.cssText="background: var(--bg-secondary); padding: 20px; border-radius: 12px; border: 1px solid var(--border); transition: transform 0.2s, box-shadow 0.2s; position: relative;",e.innerHTML=`
1082
- ${t.isShared?'<div style="position: absolute; top: 15px; right: 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 4px 10px; border-radius: 12px; font-size: 11px; font-weight: 600; display: flex; align-items: center; gap: 4px;"><span>🌐</span><span>已共享</span></div>':""}
1083
- <h3 style="margin: 0 0 10px 0; font-size: 18px; ${t.isShared?"padding-right: 80px;":""}">${t.title}</h3>
1084
- <p style="color: var(--text-secondary); margin: 0 0 15px 0; line-height: 1.6;">${t.content.substring(0,150)}${t.content.length>150?"...":""}</p>
1084
+ `;const y=document.getElementById("knowledgeList");B.length===0?y.innerHTML='<div class="empty-state" style="grid-column: 1/-1;">暂无知识条目</div>':(B.forEach(g=>{var d;const r=document.createElement("div");r.className="knowledge-card",r.style.cssText="background: var(--bg-secondary); padding: 20px; border-radius: 12px; border: 1px solid var(--border); transition: transform 0.2s, box-shadow 0.2s; position: relative;",r.innerHTML=`
1085
+ ${g.isShared?'<div style="position: absolute; top: 15px; right: 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 4px 10px; border-radius: 12px; font-size: 11px; font-weight: 600; display: flex; align-items: center; gap: 4px;"><span>🌐</span><span>已共享</span></div>':""}
1086
+ <h3 style="margin: 0 0 10px 0; font-size: 18px; ${g.isShared?"padding-right: 80px;":""}">${g.title}</h3>
1087
+ <p style="color: var(--text-secondary); margin: 0 0 15px 0; line-height: 1.6;">${g.content.substring(0,150)}${g.content.length>150?"...":""}</p>
1085
1088
  <div class="knowledge-meta" style="font-size: 12px; color: var(--text-tertiary); margin-bottom: 10px;">
1086
- <span>👤 ${((l=t.author)==null?void 0:l.username)||"未知"}</span>
1087
- <span style="margin-left: 15px;">📅 ${new Date(t.createdAt).toLocaleDateString()}</span>
1089
+ <span>👤 ${((d=g.author)==null?void 0:d.username)||"未知"}</span>
1090
+ <span style="margin-left: 15px;">📅 ${new Date(g.createdAt).toLocaleDateString()}</span>
1088
1091
  </div>
1089
- ${t.tags&&t.tags.length>0?`
1092
+ ${g.tags&&g.tags.length>0?`
1090
1093
  <div class="tags" style="display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 15px;">
1091
- ${t.tags.map(c=>`<span class="tag" style="background: var(--primary); color: white; padding: 4px 10px; border-radius: 12px; font-size: 12px;">${c}</span>`).join("")}
1094
+ ${g.tags.map(s=>`<span class="tag" style="background: var(--primary); color: white; padding: 4px 10px; border-radius: 12px; font-size: 12px;">${s}</span>`).join("")}
1092
1095
  </div>
1093
1096
  `:""}
1094
1097
  <div style="display: flex; gap: 10px;">
1095
- <button class="btn-secondary btn-sm" data-id="${t._id}" data-action="edit" style="flex: 1;">✏️ 编辑</button>
1096
- <button class="btn-danger btn-sm" data-id="${t._id}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
1098
+ <button class="btn-secondary btn-sm" data-id="${g._id}" data-action="edit" style="flex: 1;">✏️ 编辑</button>
1099
+ <button class="btn-danger btn-sm" data-id="${g._id}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
1097
1100
  </div>
1098
- `,e.onmouseenter=()=>{e.style.transform="translateY(-4px)",e.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},e.onmouseleave=()=>{e.style.transform="translateY(0)",e.style.boxShadow="none"},r.appendChild(e)}),document.querySelectorAll('[data-action="edit"]').forEach(t=>{t.addEventListener("click",async()=>{var l;const e=B.find(c=>c._id===t.dataset.id);document.getElementById("modalTitle").textContent="编辑知识条目",document.querySelector('[name="title"]').value=e.title,document.querySelector('[name="content"]').value=e.content,document.querySelector('[name="tags"]').value=((l=e.tags)==null?void 0:l.join(", "))||"",document.getElementById("isSharedCheckbox").checked=e.isShared||!1,document.getElementById("knowledgeForm").dataset.editId=e._id,document.getElementById("knowledgeModal").classList.remove("hidden")})}),document.querySelectorAll('[data-action="download"]').forEach(t=>{t.addEventListener("click",async()=>{try{const e=await fetch(`http://localhost:8765/api/backup/download/${t.dataset.filename}`,{method:"GET",headers:{Authorization:`Bearer ${n}`}});if(!e.ok)throw new Error("下载失败");const l=await e.blob(),c=window.URL.createObjectURL(l),h=document.createElement("a");h.href=c,h.download=t.dataset.filename,document.body.appendChild(h),h.click(),window.URL.revokeObjectURL(c),document.body.removeChild(h)}catch(e){alert("下载失败: "+e.message)}})}),document.querySelectorAll('[data-action="delete"]').forEach(t=>{t.addEventListener("click",async()=>{if(confirm("确定要删除这个知识条目吗?"))try{await fetch(`http://localhost:8765/api/knowledge/${t.dataset.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${n}`}}),alert("删除成功!"),await T(o)}catch(e){alert("删除失败: "+e.message)}})})),document.getElementById("createKnowledgeBtn").addEventListener("click",()=>{document.getElementById("modalTitle").textContent="创建知识条目",document.getElementById("knowledgeForm").reset(),delete document.getElementById("knowledgeForm").dataset.editId,document.getElementById("knowledgeModal").classList.remove("hidden")}),document.getElementById("closeKnowledgeModal").addEventListener("click",()=>{document.getElementById("knowledgeModal").classList.add("hidden")}),document.getElementById("knowledgeForm").addEventListener("submit",async t=>{t.preventDefault();const e=new FormData(t.target),l={title:e.get("title"),content:e.get("content"),tags:e.get("tags").split(",").map(c=>c.trim()).filter(c=>c),groupId:p._id,isShared:document.getElementById("isSharedCheckbox").checked};try{const c=t.target.dataset.editId,h=c?`http://localhost:8765/api/knowledge/${c}`:"http://localhost:8765/api/knowledge",k=await fetch(h,{method:c?"PUT":"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`},body:JSON.stringify(l)});if(!k.ok){const g=await k.json();throw new Error(g.message||"操作失败")}const A=await k.json();console.log("知识库操作结果:",A),alert(c?"更新成功!":"创建成功!"),document.getElementById("knowledgeModal").classList.add("hidden"),await T(o)}catch(c){console.error("知识库操作错误:",c),alert("操作失败: "+c.message)}})}catch(n){o.innerHTML=`<div class="empty-state">加载失败: ${n.message}</div>`}}function m(o){if(!o)return"未设置";if(typeof o=="string")return{document_create:"📄 文档创建时",document_update:"✏️ 文档更新时",document_delete:"🗑️ 文档删除时",task_create:"📋 任务创建时",task_complete:"✅ 任务完成时",task_overdue:"⏰ 任务逾期时",member_join:"👥 成员加入时",group_create:"🏢 群组创建时",scheduled:"⏱️ 定时触发",manual:"🖱️ 手动触发"}[o]||o;const i=[];if(o.event){const n={document_created:"📄 文档创建",document_updated:"✏️ 文档更新",document_deleted:"🗑️ 文档删除",task_created:"📋 任务创建",task_completed:"✅ 任务完成",task_overdue:"⏰ 任务逾期",member_joined:"👥 成员加入",group_created:"🏢 群组创建",message_sent:"💬 消息发送",file_uploaded:"📎 文件上传"};i.push(n[o.event]||o.event)}if(o.conditions&&Object.keys(o.conditions).length>0){const n=[];for(const[s,x]of Object.entries(o.conditions)){const r={group:"群组",user:"用户",keyword:"关键词",status:"状态",priority:"优先级"}[s]||s;n.push(`${r}=${x}`)}n.length>0&&i.push(`(条件: ${n.join(", ")})`)}return o.schedule&&i.push(`⏱️ 定时: ${o.schedule}`),i.length>0?i.join(" "):"自定义触发条件"}async function u(o){var i;if(!p){o.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const n=localStorage.getItem("token"),B=((i=(await(await fetch(`http://localhost:8765/api/workflows/group/${p._id}`,{headers:{Authorization:`Bearer ${n}`}})).json()).data)==null?void 0:i.workflows)||[];o.innerHTML=`
1101
+ `,r.onmouseenter=()=>{r.style.transform="translateY(-4px)",r.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},r.onmouseleave=()=>{r.style.transform="translateY(0)",r.style.boxShadow="none"},y.appendChild(r)}),document.querySelectorAll('[data-action="edit"]').forEach(g=>{g.addEventListener("click",async()=>{var d;const r=B.find(s=>s._id===g.dataset.id);document.getElementById("modalTitle").textContent="编辑知识条目",document.querySelector('[name="title"]').value=r.title,document.querySelector('[name="content"]').value=r.content,document.querySelector('[name="tags"]').value=((d=r.tags)==null?void 0:d.join(", "))||"",document.getElementById("isSharedCheckbox").checked=r.isShared||!1,document.getElementById("knowledgeForm").dataset.editId=r._id,document.getElementById("knowledgeModal").classList.remove("hidden")})}),document.querySelectorAll('[data-action="download"]').forEach(g=>{g.addEventListener("click",async()=>{try{const r=await fetch(`http://localhost:8765/api/backup/download/${g.dataset.filename}`,{method:"GET",headers:{Authorization:`Bearer ${u}`}});if(!r.ok)throw new Error("下载失败");const d=await r.blob(),s=window.URL.createObjectURL(d),c=document.createElement("a");c.href=s,c.download=g.dataset.filename,document.body.appendChild(c),c.click(),window.URL.revokeObjectURL(s),document.body.removeChild(c)}catch(r){alert("下载失败: "+r.message)}})}),document.querySelectorAll('[data-action="delete"]').forEach(g=>{g.addEventListener("click",async()=>{if(confirm("确定要删除这个知识条目吗?"))try{await fetch(`http://localhost:8765/api/knowledge/${g.dataset.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${u}`}}),alert("删除成功!"),await F(h)}catch(r){alert("删除失败: "+r.message)}})})),document.getElementById("createKnowledgeBtn").addEventListener("click",()=>{document.getElementById("modalTitle").textContent="创建知识条目",document.getElementById("knowledgeForm").reset(),delete document.getElementById("knowledgeForm").dataset.editId,document.getElementById("knowledgeModal").classList.remove("hidden")}),document.getElementById("closeKnowledgeModal").addEventListener("click",()=>{document.getElementById("knowledgeModal").classList.add("hidden")}),document.getElementById("knowledgeForm").addEventListener("submit",async g=>{g.preventDefault();const r=new FormData(g.target),d={title:r.get("title"),content:r.get("content"),tags:r.get("tags").split(",").map(s=>s.trim()).filter(s=>s),groupId:i._id,isShared:document.getElementById("isSharedCheckbox").checked};try{const s=g.target.dataset.editId,c=s?`http://localhost:8765/api/knowledge/${s}`:"http://localhost:8765/api/knowledge",w=await fetch(c,{method:s?"PUT":"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${u}`},body:JSON.stringify(d)});if(!w.ok){const p=await w.json();throw new Error(p.message||"操作失败")}const P=await w.json();console.log("知识库操作结果:",P),alert(s?"更新成功!":"创建成功!"),document.getElementById("knowledgeModal").classList.add("hidden"),await F(h)}catch(s){console.error("知识库操作错误:",s),alert("操作失败: "+s.message)}})}catch(u){h.innerHTML=`<div class="empty-state">加载失败: ${u.message}</div>`}}function x(h){if(!h)return"未设置";if(typeof h=="string")return{document_create:"📄 文档创建时",document_update:"✏️ 文档更新时",document_delete:"🗑️ 文档删除时",task_create:"📋 任务创建时",task_complete:"✅ 任务完成时",task_overdue:"⏰ 任务逾期时",member_join:"👥 成员加入时",group_create:"🏢 群组创建时",scheduled:"⏱️ 定时触发",manual:"🖱️ 手动触发"}[h]||h;const m=[];if(h.event){const u={document_created:"📄 文档创建",document_updated:"✏️ 文档更新",document_deleted:"🗑️ 文档删除",task_created:"📋 任务创建",task_completed:"✅ 任务完成",task_overdue:"⏰ 任务逾期",member_joined:"👥 成员加入",group_created:"🏢 群组创建",message_sent:"💬 消息发送",file_uploaded:"📎 文件上传"};m.push(u[h.event]||h.event)}if(h.conditions&&Object.keys(h.conditions).length>0){const u=[];for(const[b,k]of Object.entries(h.conditions)){const y={group:"群组",user:"用户",keyword:"关键词",status:"状态",priority:"优先级"}[b]||b;u.push(`${y}=${k}`)}u.length>0&&m.push(`(条件: ${u.join(", ")})`)}return h.schedule&&m.push(`⏱️ 定时: ${h.schedule}`),m.length>0?m.join(" "):"自定义触发条件"}async function f(h){var m;if(!i){h.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const u=localStorage.getItem("token"),B=((m=(await(await fetch(`http://localhost:8765/api/workflows/group/${i._id}`,{headers:{Authorization:`Bearer ${u}`}})).json()).data)==null?void 0:m.workflows)||[];h.innerHTML=`
1099
1102
  <div class="view-header">
1100
- <h2>⚙️ 工作流管理 - ${p.name}</h2>
1103
+ <h2>⚙️ 工作流管理 - ${i.name}</h2>
1101
1104
  <button class="btn-primary" id="createWorkflowBtn">➕ 创建工作流</button>
1102
1105
  </div>
1103
1106
  <div class="workflow-grid" id="workflowList" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 20px; padding: 20px;"></div>
@@ -1160,43 +1163,43 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
1160
1163
  </form>
1161
1164
  </div>
1162
1165
  </div>
1163
- `;const r=document.getElementById("workflowList");B.length===0?r.innerHTML='<div class="empty-state" style="grid-column: 1/-1;">暂无工作流</div>':(B.forEach(t=>{const e=document.createElement("div");e.className="workflow-card",e.style.cssText="background: var(--bg-secondary); padding: 20px; border-radius: 12px; border: 1px solid var(--border); transition: transform 0.2s, box-shadow 0.2s;";const l=t.status==="active";e.innerHTML=`
1166
+ `;const y=document.getElementById("workflowList");B.length===0?y.innerHTML='<div class="empty-state" style="grid-column: 1/-1;">暂无工作流</div>':(B.forEach(g=>{const r=document.createElement("div");r.className="workflow-card",r.style.cssText="background: var(--bg-secondary); padding: 20px; border-radius: 12px; border: 1px solid var(--border); transition: transform 0.2s, box-shadow 0.2s;";const d=g.status==="active";r.innerHTML=`
1164
1167
  <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 10px;">
1165
- <h3 style="margin: 0; font-size: 18px;">${t.name}</h3>
1166
- <span style="padding: 4px 10px; border-radius: 12px; font-size: 12px; background: ${l?"var(--success)":"var(--warning)"}; color: white;">${l?"✅ 活跃":"⏸️ 暂停"}</span>
1168
+ <h3 style="margin: 0; font-size: 18px;">${g.name}</h3>
1169
+ <span style="padding: 4px 10px; border-radius: 12px; font-size: 12px; background: ${d?"var(--success)":"var(--warning)"}; color: white;">${d?"✅ 活跃":"⏸️ 暂停"}</span>
1167
1170
  </div>
1168
- <p style="color: var(--text-secondary); margin: 10px 0; line-height: 1.6;">${t.description||"无描述"}</p>
1171
+ <p style="color: var(--text-secondary); margin: 10px 0; line-height: 1.6;">${g.description||"无描述"}</p>
1169
1172
  <div class="workflow-meta" style="font-size: 12px; color: var(--text-tertiary); margin: 10px 0;">
1170
- <span>🔔 触发条件: ${m(t.trigger)}</span>
1173
+ <span>🔔 触发条件: ${x(g.trigger)}</span>
1171
1174
  </div>
1172
1175
  <div style="display: flex; gap: 10px; margin-top: 15px;">
1173
- <button class="btn-secondary btn-sm" data-id="${t._id}" data-action="toggle" style="flex: 1;">
1174
- ${l?"⏸️ 暂停":"▶️ 启用"}
1176
+ <button class="btn-secondary btn-sm" data-id="${g._id}" data-action="toggle" style="flex: 1;">
1177
+ ${d?"⏸️ 暂停":"▶️ 启用"}
1175
1178
  </button>
1176
- <button class="btn-danger btn-sm" data-id="${t._id}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
1179
+ <button class="btn-danger btn-sm" data-id="${g._id}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
1177
1180
  </div>
1178
- `,e.onmouseenter=()=>{e.style.transform="translateY(-4px)",e.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},e.onmouseleave=()=>{e.style.transform="translateY(0)",e.style.boxShadow="none"},r.appendChild(e)}),document.querySelectorAll('[data-action="toggle"]').forEach(t=>{t.addEventListener("click",async()=>{try{await fetch(`http://localhost:8765/api/workflows/${t.dataset.id}/toggle`,{method:"POST",headers:{Authorization:`Bearer ${n}`}}),await u(o)}catch(e){alert("操作失败: "+e.message)}})}),document.querySelectorAll('[data-action="delete"]').forEach(t=>{t.addEventListener("click",async()=>{if(confirm("确定要删除这个工作流吗?"))try{await fetch(`http://localhost:8765/api/workflows/${t.dataset.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${n}`}}),alert("删除成功!"),await u(o)}catch(e){alert("删除失败: "+e.message)}})})),document.getElementById("createWorkflowBtn").addEventListener("click",()=>{document.getElementById("workflowModal").classList.remove("hidden")}),document.getElementById("closeWorkflowModal").addEventListener("click",()=>{document.getElementById("workflowModal").classList.add("hidden")}),document.getElementById("workflowForm").addEventListener("submit",async t=>{t.preventDefault();const e=new FormData(t.target),l={name:e.get("name"),description:e.get("description"),trigger:e.get("trigger"),groupId:p._id,actions:[]};try{await fetch("http://localhost:8765/api/workflows",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${n}`},body:JSON.stringify(l)}),alert("创建成功!"),document.getElementById("workflowModal").classList.add("hidden"),await u(o)}catch(c){alert("创建失败: "+c.message)}})}catch(n){o.innerHTML=`<div class="empty-state">加载失败: ${n.message}</div>`}}async function v(o){var i;try{const n=localStorage.getItem("token"),B=((i=(await(await fetch("http://localhost:8765/api/backup/list",{headers:{Authorization:`Bearer ${n}`}})).json()).data)==null?void 0:i.backups)||[];o.innerHTML=`
1181
+ `,r.onmouseenter=()=>{r.style.transform="translateY(-4px)",r.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},r.onmouseleave=()=>{r.style.transform="translateY(0)",r.style.boxShadow="none"},y.appendChild(r)}),document.querySelectorAll('[data-action="toggle"]').forEach(g=>{g.addEventListener("click",async()=>{try{await fetch(`http://localhost:8765/api/workflows/${g.dataset.id}/toggle`,{method:"POST",headers:{Authorization:`Bearer ${u}`}}),await f(h)}catch(r){alert("操作失败: "+r.message)}})}),document.querySelectorAll('[data-action="delete"]').forEach(g=>{g.addEventListener("click",async()=>{if(confirm("确定要删除这个工作流吗?"))try{await fetch(`http://localhost:8765/api/workflows/${g.dataset.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${u}`}}),alert("删除成功!"),await f(h)}catch(r){alert("删除失败: "+r.message)}})})),document.getElementById("createWorkflowBtn").addEventListener("click",()=>{document.getElementById("workflowModal").classList.remove("hidden")}),document.getElementById("closeWorkflowModal").addEventListener("click",()=>{document.getElementById("workflowModal").classList.add("hidden")}),document.getElementById("workflowForm").addEventListener("submit",async g=>{g.preventDefault();const r=new FormData(g.target),d={name:r.get("name"),description:r.get("description"),trigger:r.get("trigger"),groupId:i._id,actions:[]};try{await fetch("http://localhost:8765/api/workflows",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${u}`},body:JSON.stringify(d)}),alert("创建成功!"),document.getElementById("workflowModal").classList.add("hidden"),await f(h)}catch(s){alert("创建失败: "+s.message)}})}catch(u){h.innerHTML=`<div class="empty-state">加载失败: ${u.message}</div>`}}async function M(h){var m;try{const u=localStorage.getItem("token"),B=((m=(await(await fetch("http://localhost:8765/api/backup/list",{headers:{Authorization:`Bearer ${u}`}})).json()).data)==null?void 0:m.backups)||[];h.innerHTML=`
1179
1182
  <div class="view-header">
1180
1183
  <h2>💾 备份管理</h2>
1181
1184
  <button class="btn-primary" id="createBackupBtn">➕ 创建备份</button>
1182
1185
  </div>
1183
1186
  <div class="backup-grid" id="backupList" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 20px; padding: 20px;"></div>
1184
- `;const r=document.getElementById("backupList");B.length===0?r.innerHTML='<div class="empty-state" style="grid-column: 1/-1;">暂无备份</div>':(B.forEach(t=>{const e=document.createElement("div");e.className="backup-card",e.style.cssText="background: var(--bg-secondary); padding: 20px; border-radius: 12px; border: 1px solid var(--border); transition: transform 0.2s, box-shadow 0.2s;";const l=(t.size/1024/1024).toFixed(2);e.innerHTML=`
1187
+ `;const y=document.getElementById("backupList");B.length===0?y.innerHTML='<div class="empty-state" style="grid-column: 1/-1;">暂无备份</div>':(B.forEach(g=>{const r=document.createElement("div");r.className="backup-card",r.style.cssText="background: var(--bg-secondary); padding: 20px; border-radius: 12px; border: 1px solid var(--border); transition: transform 0.2s, box-shadow 0.2s;";const d=(g.size/1024/1024).toFixed(2);r.innerHTML=`
1185
1188
  <div style="display: flex; align-items: center; gap: 15px; margin-bottom: 15px;">
1186
1189
  <div style="font-size: 48px;">📦</div>
1187
1190
  <div style="flex: 1;">
1188
- <h3 style="margin: 0 0 5px 0; font-size: 16px;">${t.filename}</h3>
1189
- <p style="margin: 0; font-size: 14px; color: var(--text-secondary);">${l} MB</p>
1191
+ <h3 style="margin: 0 0 5px 0; font-size: 16px;">${g.filename}</h3>
1192
+ <p style="margin: 0; font-size: 14px; color: var(--text-secondary);">${d} MB</p>
1190
1193
  </div>
1191
1194
  </div>
1192
1195
  <div class="backup-meta" style="font-size: 12px; color: var(--text-tertiary); margin-bottom: 15px;">
1193
- <span>📅 ${new Date(t.createdAt).toLocaleString()}</span>
1196
+ <span>📅 ${new Date(g.createdAt).toLocaleString()}</span>
1194
1197
  </div>
1195
1198
  <div style="display: flex; gap: 10px;">
1196
- <button class="btn-primary btn-sm" data-backup-name="${t.name}" data-filename="${t.filename}" data-action="download" style="flex: 1;">⬇️ 下载</button>
1197
- <button class="btn-danger btn-sm" data-backup-name="${t.name}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
1199
+ <button class="btn-primary btn-sm" data-backup-name="${g.name}" data-filename="${g.filename}" data-action="download" style="flex: 1;">⬇️ 下载</button>
1200
+ <button class="btn-danger btn-sm" data-backup-name="${g.name}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
1198
1201
  </div>
1199
- `,e.onmouseenter=()=>{e.style.transform="translateY(-4px)",e.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},e.onmouseleave=()=>{e.style.transform="translateY(0)",e.style.boxShadow="none"},r.appendChild(e)}),document.querySelectorAll('[data-action="download"]').forEach(t=>{t.addEventListener("click",async()=>{try{const e=t.dataset.backupName,l=t.dataset.filename,c=localStorage.getItem("token");console.log("开始下载备份:",{backupName:e,filename:l});const h=await fetch(`http://localhost:8765/api/backup/download/${e}`,{method:"GET",headers:{Authorization:`Bearer ${c}`}});if(!h.ok)throw new Error(`下载失败: ${h.status} ${h.statusText}`);const E=await h.blob();console.log("文件下载成功,大小:",E.size,"bytes");const k=window.URL.createObjectURL(E),A=document.createElement("a");A.href=k,A.download=l,A.style.display="none",document.body.appendChild(A),A.click(),setTimeout(()=>{document.body.removeChild(A),window.URL.revokeObjectURL(k)},100);const g=t.textContent;t.textContent="✅ 下载成功",t.disabled=!0,setTimeout(()=>{t.textContent=g,t.disabled=!1},2e3)}catch(e){console.error("下载失败:",e),alert("下载失败: "+e.message),t.textContent="⬇️ 下载",t.disabled=!1}})}),document.querySelectorAll('[data-action="delete"]').forEach(t=>{t.addEventListener("click",async()=>{if(confirm("确定要删除这个备份吗?"))try{const e=t.dataset.backupName;console.log("删除备份:",e);const l=await fetch(`http://localhost:8765/api/backup/${e}`,{method:"DELETE",headers:{Authorization:`Bearer ${n}`}});if(!l.ok)throw new Error(`删除失败: ${l.status}`);alert("删除成功!"),await v(o)}catch(e){console.error("删除失败:",e),alert("删除失败: "+e.message)}})})),document.getElementById("createBackupBtn").addEventListener("click",async()=>{if(confirm("确定要创建新备份吗?这可能需要一些时间。")){const t=document.getElementById("createBackupBtn");t.disabled=!0,t.textContent="⏳ 创建中...";try{await fetch("http://localhost:8765/api/backup/create",{method:"POST",headers:{Authorization:`Bearer ${n}`}}),alert("备份创建成功!"),await v(o)}catch(e){alert("创建失败: "+e.message)}finally{t.disabled=!1,t.textContent="➕ 创建备份"}}})}catch(n){o.innerHTML=`<div class="empty-state">加载失败: ${n.message}</div>`}}async function w(o){o.innerHTML='<div class="empty-state">AI助手功能开发中...</div>'}async function $(o){o.innerHTML=`
1202
+ `,r.onmouseenter=()=>{r.style.transform="translateY(-4px)",r.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},r.onmouseleave=()=>{r.style.transform="translateY(0)",r.style.boxShadow="none"},y.appendChild(r)}),document.querySelectorAll('[data-action="download"]').forEach(g=>{g.addEventListener("click",async()=>{try{const r=g.dataset.backupName,d=g.dataset.filename,s=localStorage.getItem("token");console.log("开始下载备份:",{backupName:r,filename:d});const c=await fetch(`http://localhost:8765/api/backup/download/${r}`,{method:"GET",headers:{Authorization:`Bearer ${s}`}});if(!c.ok)throw new Error(`下载失败: ${c.status} ${c.statusText}`);const v=await c.blob();console.log("文件下载成功,大小:",v.size,"bytes");const w=window.URL.createObjectURL(v),P=document.createElement("a");P.href=w,P.download=d,P.style.display="none",document.body.appendChild(P),P.click(),setTimeout(()=>{document.body.removeChild(P),window.URL.revokeObjectURL(w)},100);const p=g.textContent;g.textContent="✅ 下载成功",g.disabled=!0,setTimeout(()=>{g.textContent=p,g.disabled=!1},2e3)}catch(r){console.error("下载失败:",r),alert("下载失败: "+r.message),g.textContent="⬇️ 下载",g.disabled=!1}})}),document.querySelectorAll('[data-action="delete"]').forEach(g=>{g.addEventListener("click",async()=>{if(confirm("确定要删除这个备份吗?"))try{const r=g.dataset.backupName;console.log("删除备份:",r);const d=await fetch(`http://localhost:8765/api/backup/${r}`,{method:"DELETE",headers:{Authorization:`Bearer ${u}`}});if(!d.ok)throw new Error(`删除失败: ${d.status}`);alert("删除成功!"),await M(h)}catch(r){console.error("删除失败:",r),alert("删除失败: "+r.message)}})})),document.getElementById("createBackupBtn").addEventListener("click",async()=>{if(confirm("确定要创建新备份吗?这可能需要一些时间。")){const g=document.getElementById("createBackupBtn");g.disabled=!0,g.textContent="⏳ 创建中...";try{await fetch("http://localhost:8765/api/backup/create",{method:"POST",headers:{Authorization:`Bearer ${u}`}}),alert("备份创建成功!"),await M(h)}catch(r){alert("创建失败: "+r.message)}finally{g.disabled=!1,g.textContent="➕ 创建备份"}}})}catch(u){h.innerHTML=`<div class="empty-state">加载失败: ${u.message}</div>`}}async function A(h){h.innerHTML='<div class="empty-state">AI助手功能开发中...</div>'}async function I(h){h.innerHTML=`
1200
1203
  <div class="view-header">
1201
1204
  <h2>📤 数据导出</h2>
1202
1205
  </div>
@@ -1241,7 +1244,7 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
1241
1244
  <div id="historyList">加载中...</div>
1242
1245
  </div>
1243
1246
  </div>
1244
- `;try{const i=localStorage.getItem("token"),s=await(await fetch("http://localhost:8765/api/export/history",{headers:{Authorization:`Bearer ${i}`}})).json(),x=document.getElementById("historyList");s.exports&&s.exports.length>0?x.innerHTML=s.exports.map(B=>`
1247
+ `;try{const m=localStorage.getItem("token"),b=await(await fetch("http://localhost:8765/api/export/history",{headers:{Authorization:`Bearer ${m}`}})).json(),k=document.getElementById("historyList");b.exports&&b.exports.length>0?k.innerHTML=b.exports.map(B=>`
1245
1248
  <div class="export-item" style="display: flex; justify-content: space-between; align-items: center; padding: 15px; background: var(--bg); border-radius: 8px; margin-bottom: 10px;">
1246
1249
  <div>
1247
1250
  <div style="font-weight: 600; margin-bottom: 5px;">📦 ${B.format.toUpperCase()} 导出</div>
@@ -1249,9 +1252,9 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
1249
1252
  </div>
1250
1253
  <a href="http://localhost:8765/api/export/download/${B.filename}" class="btn-sm btn-primary" download style="text-decoration: none;">⬇️ 下载</a>
1251
1254
  </div>
1252
- `).join(""):x.innerHTML='<div class="empty-state">暂无导出记录</div>'}catch{document.getElementById("historyList").innerHTML='<div class="empty-state">加载失败</div>'}document.getElementById("exportBtn").addEventListener("click",async()=>{const i={groups:document.getElementById("exportGroups").checked,documents:document.getElementById("exportDocuments").checked,tasks:document.getElementById("exportTasks").checked,messages:document.getElementById("exportMessages").checked,files:document.getElementById("exportFiles").checked,format:document.getElementById("exportFormat").value},n=document.getElementById("exportBtn");n.disabled=!0,n.textContent="⏳ 导出中...";try{const s=localStorage.getItem("token"),x=await fetch("http://localhost:8765/api/export",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${s}`},body:JSON.stringify(i)});if(x.ok){const B=await x.blob(),r=window.URL.createObjectURL(B),t=document.createElement("a");t.href=r,t.download=`export-${Date.now()}.${i.format}`,t.click(),alert("导出成功!"),await $(o)}else throw new Error("导出失败")}catch(s){alert("导出失败: "+s.message)}finally{n.disabled=!1,n.textContent="🚀 开始导出"}})}async function C(o){if(!p){o.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}o.innerHTML=`
1255
+ `).join(""):k.innerHTML='<div class="empty-state">暂无导出记录</div>'}catch{document.getElementById("historyList").innerHTML='<div class="empty-state">加载失败</div>'}document.getElementById("exportBtn").addEventListener("click",async()=>{const m={groups:document.getElementById("exportGroups").checked,documents:document.getElementById("exportDocuments").checked,tasks:document.getElementById("exportTasks").checked,messages:document.getElementById("exportMessages").checked,files:document.getElementById("exportFiles").checked,format:document.getElementById("exportFormat").value},u=document.getElementById("exportBtn");u.disabled=!0,u.textContent="⏳ 导出中...";try{const b=localStorage.getItem("token"),k=await fetch("http://localhost:8765/api/export",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${b}`},body:JSON.stringify(m)});if(k.ok){const B=await k.blob(),y=window.URL.createObjectURL(B),g=document.createElement("a");g.href=y,g.download=`export-${Date.now()}.${m.format}`,g.click(),alert("导出成功!"),await I(h)}else throw new Error("导出失败")}catch(b){alert("导出失败: "+b.message)}finally{u.disabled=!1,u.textContent="🚀 开始导出"}})}async function z(h){if(!i){h.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}h.innerHTML=`
1253
1256
  <div class="view-header">
1254
- <h2>🎨 协作白板 - ${p.name}</h2>
1257
+ <h2>🎨 协作白板 - ${i.name}</h2>
1255
1258
  <div style="display: flex; gap: 10px;">
1256
1259
  <button class="btn-secondary" id="clearCanvas">清空画布</button>
1257
1260
  <button class="btn-primary" id="saveCanvas">保存白板</button>
@@ -1268,10 +1271,10 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
1268
1271
  </div>
1269
1272
  <canvas id="whiteboard" width="1200" height="600" style="border: 1px solid var(--border); background: white; cursor: crosshair;"></canvas>
1270
1273
  <div class="whiteboard-users" id="whiteboardUsers">
1271
- <span class="user-badge">👤 ${_.username}</span>
1274
+ <span class="user-badge">👤 ${t.username}</span>
1272
1275
  </div>
1273
1276
  </div>
1274
- `;const i=document.getElementById("whiteboard"),n=i.getContext("2d");let s=!1,x="pen",B="#000000",r=3;document.querySelectorAll(".tool-btn").forEach(l=>{l.addEventListener("click",()=>{document.querySelectorAll(".tool-btn").forEach(c=>c.classList.remove("active")),l.classList.add("active"),x=l.dataset.tool})}),document.getElementById("colorPicker").addEventListener("change",l=>{B=l.target.value}),document.getElementById("brushSize").addEventListener("input",l=>{r=l.target.value});let t=0,e=0;i.addEventListener("mousedown",l=>{s=!0;const c=i.getBoundingClientRect();t=l.clientX-c.left,e=l.clientY-c.top}),i.addEventListener("mousemove",l=>{if(!s)return;const c=i.getBoundingClientRect(),h=l.clientX-c.left,E=l.clientY-c.top;n.beginPath(),n.moveTo(t,e),n.lineTo(h,E),n.strokeStyle=x==="eraser"?"#ffffff":B,n.lineWidth=r,n.lineCap="round",n.stroke(),t=h,e=E,a.sendWhiteboardData(p._id,{tool:x,color:B,size:r,from:{x:t,y:e},to:{x:h,y:E}})}),i.addEventListener("mouseup",()=>{s=!1}),i.addEventListener("mouseleave",()=>{s=!1}),document.getElementById("clearCanvas").addEventListener("click",()=>{confirm("确定要清空画布吗?")&&n.clearRect(0,0,i.width,i.height)}),document.getElementById("saveCanvas").addEventListener("click",()=>{const l=i.toDataURL("image/png"),c=document.createElement("a");c.download=`whiteboard-${Date.now()}.png`,c.href=l,c.click(),alert("白板已保存!")}),a.on("whiteboard_draw",l=>{l.groupId===p._id&&l.userId!==D&&(n.beginPath(),n.moveTo(l.from.x,l.from.y),n.lineTo(l.to.x,l.to.y),n.strokeStyle=l.tool==="eraser"?"#ffffff":l.color,n.lineWidth=l.size,n.lineCap="round",n.stroke())})}async function j(o){o.innerHTML=`
1277
+ `;const m=document.getElementById("whiteboard"),u=m.getContext("2d");let b=!1,k="pen",B="#000000",y=3;document.querySelectorAll(".tool-btn").forEach(d=>{d.addEventListener("click",()=>{document.querySelectorAll(".tool-btn").forEach(s=>s.classList.remove("active")),d.classList.add("active"),k=d.dataset.tool})}),document.getElementById("colorPicker").addEventListener("change",d=>{B=d.target.value}),document.getElementById("brushSize").addEventListener("input",d=>{y=d.target.value});let g=0,r=0;m.addEventListener("mousedown",d=>{b=!0;const s=m.getBoundingClientRect();g=d.clientX-s.left,r=d.clientY-s.top}),m.addEventListener("mousemove",d=>{if(!b)return;const s=m.getBoundingClientRect(),c=d.clientX-s.left,v=d.clientY-s.top;u.beginPath(),u.moveTo(g,r),u.lineTo(c,v),u.strokeStyle=k==="eraser"?"#ffffff":B,u.lineWidth=y,u.lineCap="round",u.stroke(),g=c,r=v,e.sendWhiteboardData(i._id,{tool:k,color:B,size:y,from:{x:g,y:r},to:{x:c,y:v}})}),m.addEventListener("mouseup",()=>{b=!1}),m.addEventListener("mouseleave",()=>{b=!1}),document.getElementById("clearCanvas").addEventListener("click",()=>{confirm("确定要清空画布吗?")&&u.clearRect(0,0,m.width,m.height)}),document.getElementById("saveCanvas").addEventListener("click",()=>{const d=m.toDataURL("image/png"),s=document.createElement("a");s.download=`whiteboard-${Date.now()}.png`,s.href=d,s.click(),alert("白板已保存!")}),e.on("whiteboard_draw",d=>{d.groupId===i._id&&d.userId!==o&&(u.beginPath(),u.moveTo(d.from.x,d.from.y),u.lineTo(d.to.x,d.to.y),u.strokeStyle=d.tool==="eraser"?"#ffffff":d.color,u.lineWidth=d.size,u.lineCap="round",u.stroke())})}async function W(h){h.innerHTML=`
1275
1278
  <div class="view-header" style="margin-bottom: 30px;">
1276
1279
  <h2 style="display: flex; align-items: center; gap: 12px; font-size: 28px;">
1277
1280
  <span style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">⚙️ 设置中心</span>
@@ -1287,11 +1290,11 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
1287
1290
  </div>
1288
1291
  <div class="setting-item" style="margin-bottom: 20px;">
1289
1292
  <label style="display: block; margin-bottom: 8px; font-weight: 600; color: var(--text-secondary); font-size: 14px;">👤 用户名</label>
1290
- <input type="text" value="${_.username}" disabled style="width: 100%; padding: 12px 16px; border: 2px solid var(--border); border-radius: 10px; font-size: 14px; background: var(--bg-tertiary); cursor: not-allowed;">
1293
+ <input type="text" value="${t.username}" disabled style="width: 100%; padding: 12px 16px; border: 2px solid var(--border); border-radius: 10px; font-size: 14px; background: var(--bg-tertiary); cursor: not-allowed;">
1291
1294
  </div>
1292
1295
  <div class="setting-item" style="margin-bottom: 20px;">
1293
1296
  <label style="display: block; margin-bottom: 8px; font-weight: 600; color: var(--text-secondary); font-size: 14px;">📧 邮箱</label>
1294
- <input type="email" id="userEmail" value="${_.email||""}" placeholder="请输入邮箱地址" style="width: 100%; padding: 12px 16px; border: 2px solid var(--border); border-radius: 10px; font-size: 14px; transition: border-color 0.2s;">
1297
+ <input type="email" id="userEmail" value="${t.email||""}" placeholder="请输入邮箱地址" style="width: 100%; padding: 12px 16px; border: 2px solid var(--border); border-radius: 10px; font-size: 14px; transition: border-color 0.2s;">
1295
1298
  </div>
1296
1299
  <div class="setting-item">
1297
1300
  <label style="display: block; margin-bottom: 8px; font-weight: 600; color: var(--text-secondary); font-size: 14px;">🔐 密码</label>
@@ -1367,7 +1370,7 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
1367
1370
  <button class="btn-primary" id="saveSettingsBtn" style="padding: 12px 32px; border-radius: 10px; font-weight: 600; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border: none; cursor: pointer; transition: transform 0.2s;">💾 保存设置</button>
1368
1371
  </div>
1369
1372
  </div>
1370
- `,document.querySelectorAll(".settings-card").forEach(n=>{n.addEventListener("mouseenter",()=>{n.style.transform="translateY(-4px)",n.style.boxShadow="0 8px 24px rgba(0,0,0,0.12)"}),n.addEventListener("mouseleave",()=>{n.style.transform="translateY(0)",n.style.boxShadow="0 2px 8px rgba(0,0,0,0.05)"})}),document.querySelectorAll('input[type="email"], select').forEach(n=>{n.addEventListener("focus",()=>{n.style.borderColor="var(--primary)"}),n.addEventListener("blur",()=>{n.style.borderColor="var(--border)"})}),document.getElementById("changePasswordBtn").addEventListener("click",()=>{prompt("请输入新密码:")&&alert("密码修改功能开发中...")}),document.getElementById("resetSettingsBtn").addEventListener("click",()=>{confirm("确定要重置所有设置吗?")&&location.reload()}),document.getElementById("saveSettingsBtn").addEventListener("click",()=>{const n={email:document.getElementById("userEmail").value,emailNotifications:document.getElementById("emailNotifications").checked,desktopNotifications:document.getElementById("desktopNotifications").checked,soundNotifications:document.getElementById("soundNotifications").checked,theme:document.getElementById("themeSelect").value,language:document.getElementById("languageSelect").value};localStorage.setItem("userSettings",JSON.stringify(n)),M(n.theme),alert("✅ 设置已保存!")}),document.getElementById("themeSelect").addEventListener("change",n=>{M(n.target.value)});const i=localStorage.getItem("userSettings");if(i){const n=JSON.parse(i);n.email&&(document.getElementById("userEmail").value=n.email),document.getElementById("emailNotifications").checked=n.emailNotifications!==!1,document.getElementById("desktopNotifications").checked=n.desktopNotifications!==!1,document.getElementById("soundNotifications").checked=n.soundNotifications!==!1,n.theme&&(document.getElementById("themeSelect").value=n.theme,M(n.theme)),n.language&&(document.getElementById("languageSelect").value=n.language)}}function M(o){const i=document.documentElement,n={dark:{primary:"#6366f1",primaryDark:"#4f46e5",secondary:"#8b5cf6",bgDark:"#0f172a",bgCard:"#1e293b",bgHover:"#334155",textPrimary:"#f1f5f9",textSecondary:"#94a3b8",border:"#334155"},blue:{primary:"#3b82f6",primaryDark:"#2563eb",secondary:"#06b6d4",bgDark:"#0c1222",bgCard:"#1a2332",bgHover:"#2a3442",textPrimary:"#e0f2fe",textSecondary:"#7dd3fc",border:"#2a3442"},green:{primary:"#10b981",primaryDark:"#059669",secondary:"#34d399",bgDark:"#0a1f1a",bgCard:"#1a2f2a",bgHover:"#2a3f3a",textPrimary:"#d1fae5",textSecondary:"#6ee7b7",border:"#2a3f3a"},orange:{primary:"#f59e0b",primaryDark:"#d97706",secondary:"#fb923c",bgDark:"#1f1a0a",bgCard:"#2f2a1a",bgHover:"#3f3a2a",textPrimary:"#fef3c7",textSecondary:"#fcd34d",border:"#3f3a2a"},pink:{primary:"#ec4899",primaryDark:"#db2777",secondary:"#f472b6",bgDark:"#1f0a1a",bgCard:"#2f1a2a",bgHover:"#3f2a3a",textPrimary:"#fce7f3",textSecondary:"#f9a8d4",border:"#3f2a3a"},light:{primary:"#6366f1",primaryDark:"#4f46e5",secondary:"#8b5cf6",bgDark:"#ffffff",bgCard:"#f8fafc",bgHover:"#e2e8f0",textPrimary:"#0f172a",textSecondary:"#475569",border:"#cbd5e1"}},s=n[o]||n.dark;i.style.setProperty("--primary",s.primary),i.style.setProperty("--primary-dark",s.primaryDark),i.style.setProperty("--secondary",s.secondary),i.style.setProperty("--bg-dark",s.bgDark),i.style.setProperty("--bg-card",s.bgCard),i.style.setProperty("--bg-hover",s.bgHover),i.style.setProperty("--text-primary",s.textPrimary),i.style.setProperty("--text-secondary",s.textSecondary),i.style.setProperty("--border",s.border),localStorage.setItem("currentTheme",o)}async function V(o){o.innerHTML=`
1373
+ `,document.querySelectorAll(".settings-card").forEach(u=>{u.addEventListener("mouseenter",()=>{u.style.transform="translateY(-4px)",u.style.boxShadow="0 8px 24px rgba(0,0,0,0.12)"}),u.addEventListener("mouseleave",()=>{u.style.transform="translateY(0)",u.style.boxShadow="0 2px 8px rgba(0,0,0,0.05)"})}),document.querySelectorAll('input[type="email"], select').forEach(u=>{u.addEventListener("focus",()=>{u.style.borderColor="var(--primary)"}),u.addEventListener("blur",()=>{u.style.borderColor="var(--border)"})}),document.getElementById("changePasswordBtn").addEventListener("click",()=>{prompt("请输入新密码:")&&alert("密码修改功能开发中...")}),document.getElementById("resetSettingsBtn").addEventListener("click",()=>{confirm("确定要重置所有设置吗?")&&location.reload()}),document.getElementById("saveSettingsBtn").addEventListener("click",()=>{const u={email:document.getElementById("userEmail").value,emailNotifications:document.getElementById("emailNotifications").checked,desktopNotifications:document.getElementById("desktopNotifications").checked,soundNotifications:document.getElementById("soundNotifications").checked,theme:document.getElementById("themeSelect").value,language:document.getElementById("languageSelect").value};localStorage.setItem("userSettings",JSON.stringify(u)),O(u.theme),alert("✅ 设置已保存!")}),document.getElementById("themeSelect").addEventListener("change",u=>{O(u.target.value)});const m=localStorage.getItem("userSettings");if(m){const u=JSON.parse(m);u.email&&(document.getElementById("userEmail").value=u.email),document.getElementById("emailNotifications").checked=u.emailNotifications!==!1,document.getElementById("desktopNotifications").checked=u.desktopNotifications!==!1,document.getElementById("soundNotifications").checked=u.soundNotifications!==!1,u.theme&&(document.getElementById("themeSelect").value=u.theme,O(u.theme)),u.language&&(document.getElementById("languageSelect").value=u.language)}}function O(h){const m=document.documentElement,u={dark:{primary:"#6366f1",primaryDark:"#4f46e5",secondary:"#8b5cf6",bgDark:"#0f172a",bgCard:"#1e293b",bgHover:"#334155",textPrimary:"#f1f5f9",textSecondary:"#94a3b8",border:"#334155"},blue:{primary:"#3b82f6",primaryDark:"#2563eb",secondary:"#06b6d4",bgDark:"#0c1222",bgCard:"#1a2332",bgHover:"#2a3442",textPrimary:"#e0f2fe",textSecondary:"#7dd3fc",border:"#2a3442"},green:{primary:"#10b981",primaryDark:"#059669",secondary:"#34d399",bgDark:"#0a1f1a",bgCard:"#1a2f2a",bgHover:"#2a3f3a",textPrimary:"#d1fae5",textSecondary:"#6ee7b7",border:"#2a3f3a"},orange:{primary:"#f59e0b",primaryDark:"#d97706",secondary:"#fb923c",bgDark:"#1f1a0a",bgCard:"#2f2a1a",bgHover:"#3f3a2a",textPrimary:"#fef3c7",textSecondary:"#fcd34d",border:"#3f3a2a"},pink:{primary:"#ec4899",primaryDark:"#db2777",secondary:"#f472b6",bgDark:"#1f0a1a",bgCard:"#2f1a2a",bgHover:"#3f2a3a",textPrimary:"#fce7f3",textSecondary:"#f9a8d4",border:"#3f2a3a"},light:{primary:"#6366f1",primaryDark:"#4f46e5",secondary:"#8b5cf6",bgDark:"#ffffff",bgCard:"#f8fafc",bgHover:"#e2e8f0",textPrimary:"#0f172a",textSecondary:"#475569",border:"#cbd5e1"}},b=u[h]||u.dark;m.style.setProperty("--primary",b.primary),m.style.setProperty("--primary-dark",b.primaryDark),m.style.setProperty("--secondary",b.secondary),m.style.setProperty("--bg-dark",b.bgDark),m.style.setProperty("--bg-card",b.bgCard),m.style.setProperty("--bg-hover",b.bgHover),m.style.setProperty("--text-primary",b.textPrimary),m.style.setProperty("--text-secondary",b.textSecondary),m.style.setProperty("--border",b.border),localStorage.setItem("currentTheme",h)}async function oe(h){h.innerHTML=`
1371
1374
  <div class="view-header" style="margin-bottom: 30px;">
1372
1375
  <h2 style="display: flex; align-items: center; gap: 12px; font-size: 28px;">
1373
1376
  <span style="background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent;">❓ 帮助中心</span>
@@ -1547,29 +1550,29 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
1547
1550
  </div>
1548
1551
  </div>
1549
1552
  </div>
1550
- `,document.querySelectorAll(".help-card").forEach(i=>{i.addEventListener("mouseenter",()=>{i.style.transform="translateY(-4px)",i.style.boxShadow="0 8px 24px rgba(0,0,0,0.12)"}),i.addEventListener("mouseleave",()=>{i.style.transform="translateY(0)";const n=i.style.background.includes("gradient");i.style.boxShadow=n?"0 4px 12px rgba(102, 126, 234, 0.3)":"0 2px 8px rgba(0,0,0,0.05)"})})}async function F(o){const i=document.getElementById("contentArea");switch(o){case"groups":await te(i);break;case"tasks":await K(i);break;case"documents":await J(i);break;case"chat":await pe(i);break;case"files":await X(i);break;case"search":await re(i);break;case"call":await ge(i);break;case"audit":await ie(i);break;case"knowledge":await T(i);break;case"workflow":await u(i);break;case"backup":await v(i);break;case"export":await $(i);break;case"ai":await w(i);break;case"export":await $(i);break;case"whiteboard":await C(i);break;case"settings":await j(i);break;case"help":await V(i);break}}F("groups")}function ke(_){const a=document.documentElement,y={dark:{primary:"#6366f1",primaryDark:"#4f46e5",secondary:"#8b5cf6",bgDark:"#0f172a",bgCard:"#1e293b",bgHover:"#334155",textPrimary:"#f1f5f9",textSecondary:"#94a3b8",border:"#334155"},blue:{primary:"#3b82f6",primaryDark:"#2563eb",secondary:"#06b6d4",bgDark:"#0c1222",bgCard:"#1a2332",bgHover:"#2a3442",textPrimary:"#e0f2fe",textSecondary:"#7dd3fc",border:"#2a3442"},green:{primary:"#10b981",primaryDark:"#059669",secondary:"#34d399",bgDark:"#0a1f1a",bgCard:"#1a2f2a",bgHover:"#2a3f3a",textPrimary:"#d1fae5",textSecondary:"#6ee7b7",border:"#2a3f3a"},orange:{primary:"#f59e0b",primaryDark:"#d97706",secondary:"#fb923c",bgDark:"#1f1a0a",bgCard:"#2f2a1a",bgHover:"#3f3a2a",textPrimary:"#fef3c7",textSecondary:"#fcd34d",border:"#3f3a2a"},pink:{primary:"#ec4899",primaryDark:"#db2777",secondary:"#f472b6",bgDark:"#1f0a1a",bgCard:"#2f1a2a",bgHover:"#3f2a3a",textPrimary:"#fce7f3",textSecondary:"#f9a8d4",border:"#3f2a3a"},light:{primary:"#6366f1",primaryDark:"#4f46e5",secondary:"#8b5cf6",bgDark:"#ffffff",bgCard:"#f8fafc",bgHover:"#e2e8f0",textPrimary:"#0f172a",textSecondary:"#475569",border:"#cbd5e1"}},d=y[_]||y.dark;a.style.setProperty("--primary",d.primary),a.style.setProperty("--primary-dark",d.primaryDark),a.style.setProperty("--secondary",d.secondary),a.style.setProperty("--bg-dark",d.bgDark),a.style.setProperty("--bg-card",d.bgCard),a.style.setProperty("--bg-hover",d.bgHover),a.style.setProperty("--text-primary",d.textPrimary),a.style.setProperty("--text-secondary",d.textSecondary),a.style.setProperty("--border",d.border)}function Ee(_,a){const y=document.getElementById("app"),d=new be,z=new oe,D=_.id||_._id;let p=null,R=[];function se(f){if(f.startsWith("[白板作品]")){const b=f.replace("[白板作品]","").trim(),T=b.includes("/api/files/")&&b.includes("/download");let m=b;if(T&&!b.includes("token=")){const u=localStorage.getItem("token");m=b.includes("?")?`${b}&token=${u}`:`${b}?token=${u}`}return`
1553
+ `,document.querySelectorAll(".help-card").forEach(m=>{m.addEventListener("mouseenter",()=>{m.style.transform="translateY(-4px)",m.style.boxShadow="0 8px 24px rgba(0,0,0,0.12)"}),m.addEventListener("mouseleave",()=>{m.style.transform="translateY(0)";const u=m.style.background.includes("gradient");m.style.boxShadow=u?"0 4px 12px rgba(102, 126, 234, 0.3)":"0 2px 8px rgba(0,0,0,0.05)"})})}async function K(h){const m=document.getElementById("contentArea");switch(h){case"groups":await $(m);break;case"tasks":await _(m);break;case"documents":await R(m);break;case"chat":await U(m);break;case"files":await Z(m);break;case"search":await ne(m);break;case"call":await V(m);break;case"audit":await Q(m);break;case"knowledge":await F(m);break;case"workflow":await f(m);break;case"backup":await M(m);break;case"export":await I(m);break;case"ai":await A(m);break;case"export":await I(m);break;case"whiteboard":await z(m);break;case"settings":await W(m);break;case"help":await oe(m);break}}K("groups")}function un(t){const e=document.documentElement,n={dark:{primary:"#6366f1",primaryDark:"#4f46e5",secondary:"#8b5cf6",bgDark:"#0f172a",bgCard:"#1e293b",bgHover:"#334155",textPrimary:"#f1f5f9",textSecondary:"#94a3b8",border:"#334155"},blue:{primary:"#3b82f6",primaryDark:"#2563eb",secondary:"#06b6d4",bgDark:"#0c1222",bgCard:"#1a2332",bgHover:"#2a3442",textPrimary:"#e0f2fe",textSecondary:"#7dd3fc",border:"#2a3442"},green:{primary:"#10b981",primaryDark:"#059669",secondary:"#34d399",bgDark:"#0a1f1a",bgCard:"#1a2f2a",bgHover:"#2a3f3a",textPrimary:"#d1fae5",textSecondary:"#6ee7b7",border:"#2a3f3a"},orange:{primary:"#f59e0b",primaryDark:"#d97706",secondary:"#fb923c",bgDark:"#1f1a0a",bgCard:"#2f2a1a",bgHover:"#3f3a2a",textPrimary:"#fef3c7",textSecondary:"#fcd34d",border:"#3f3a2a"},pink:{primary:"#ec4899",primaryDark:"#db2777",secondary:"#f472b6",bgDark:"#1f0a1a",bgCard:"#2f1a2a",bgHover:"#3f2a3a",textPrimary:"#fce7f3",textSecondary:"#f9a8d4",border:"#3f2a3a"},light:{primary:"#6366f1",primaryDark:"#4f46e5",secondary:"#8b5cf6",bgDark:"#ffffff",bgCard:"#f8fafc",bgHover:"#e2e8f0",textPrimary:"#0f172a",textSecondary:"#475569",border:"#cbd5e1"}},a=n[t]||n.dark;e.style.setProperty("--primary",a.primary),e.style.setProperty("--primary-dark",a.primaryDark),e.style.setProperty("--secondary",a.secondary),e.style.setProperty("--bg-dark",a.bgDark),e.style.setProperty("--bg-card",a.bgCard),e.style.setProperty("--bg-hover",a.bgHover),e.style.setProperty("--text-primary",a.textPrimary),e.style.setProperty("--text-secondary",a.textSecondary),e.style.setProperty("--border",a.border)}function gn(t,e){const n=document.getElementById("app"),a=new ct,l=new Be,o=t.id||t._id;let i=null,S=[];function N(T){if(T.startsWith("[白板作品]")){const E=T.replace("[白板作品]","").trim(),F=E.includes("/api/files/")&&E.includes("/download");let x=E;if(F&&!E.includes("token=")){const f=localStorage.getItem("token");x=E.includes("?")?`${E}&token=${f}`:`${E}?token=${f}`}return`
1551
1554
  <div style="background: linear-gradient(135deg, rgb(99, 102, 241) 0%, rgb(139, 92, 246) 100%); padding: 16px; border-radius: 12px; box-shadow: 0 4px 12px rgba(99, 102, 241, 0.3);">
1552
1555
  <div style="margin-bottom: 12px; font-weight: 600; color: white; display: flex; align-items: center; gap: 6px; font-size: 15px;">
1553
1556
  <span style="font-size: 18px;">🎨</span>
1554
1557
  <span>白板作品</span>
1555
1558
  </div>
1556
1559
  <div style="position: relative; display: inline-block;">
1557
- <img src="${m}" alt="白板作品"
1560
+ <img src="${x}" alt="白板作品"
1558
1561
  style="max-width: 400px; max-height: 400px; border-radius: 8px; cursor: pointer; box-shadow: 0 2px 8px rgba(0,0,0,0.2); background: rgba(255,255,255,0.1);"
1559
- onclick="window.open('${m}', '_blank')"
1560
- onerror="this.style.display='none'; this.nextElementSibling.style.display='block'; console.error('白板图片加载失败:', '${m}');"
1561
- onload="console.log('白板图片加载成功:', '${m}');">
1562
+ onclick="window.open('${x}', '_blank')"
1563
+ onerror="this.style.display='none'; this.nextElementSibling.style.display='block'; console.error('白板图片加载失败:', '${x}');"
1564
+ onload="console.log('白板图片加载成功:', '${x}');">
1562
1565
  <div style="display: none; padding: 20px; background: rgba(255,255,255,0.1); border: 2px dashed rgba(255,255,255,0.3); border-radius: 8px; text-align: center; color: white;">
1563
1566
  <div style="font-size: 48px; margin-bottom: 10px;">⚠️</div>
1564
1567
  <div style="font-weight: 600; margin-bottom: 5px;">图片加载失败</div>
1565
1568
  <div style="font-size: 12px;">图片可能已被删除或URL无效</div>
1566
- <button onclick="navigator.clipboard.writeText('${b}'); alert('图片URL已复制到剪贴板');" style="margin-top: 10px; padding: 6px 12px; background: rgba(255,255,255,0.2); color: white; border: none; border-radius: 6px; cursor: pointer;">复制图片URL</button>
1569
+ <button onclick="navigator.clipboard.writeText('${E}'); alert('图片URL已复制到剪贴板');" style="margin-top: 10px; padding: 6px 12px; background: rgba(255,255,255,0.2); color: white; border: none; border-radius: 6px; cursor: pointer;">复制图片URL</button>
1567
1570
  </div>
1568
1571
  <div style="margin-top: 10px; font-size: 12px; color: rgba(255,255,255,0.8);">点击图片查看大图</div>
1569
1572
  </div>
1570
1573
  </div>
1571
- `}if(f.startsWith("[投票]")){const b=f.replace("[投票]","").trim();return`
1572
- <div class="poll-card" data-poll-id="${b}" style="background: linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(168, 85, 247, 0.1) 100%); border: 2px solid rgba(99, 102, 241, 0.3); border-radius: 12px; padding: 16px; cursor: pointer; transition: all 0.3s;" onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 4px 12px rgba(99, 102, 241, 0.2)'" onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='none'" onclick="viewPollDetail('${b}')">
1574
+ `}if(T.startsWith("[投票]")){const E=T.replace("[投票]","").trim();return`
1575
+ <div class="poll-card" data-poll-id="${E}" style="background: linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(168, 85, 247, 0.1) 100%); border: 2px solid rgba(99, 102, 241, 0.3); border-radius: 12px; padding: 16px; cursor: pointer; transition: all 0.3s;" onmouseover="this.style.transform='translateY(-2px)'; this.style.boxShadow='0 4px 12px rgba(99, 102, 241, 0.2)'" onmouseout="this.style.transform='translateY(0)'; this.style.boxShadow='none'" onclick="viewPollDetail('${E}')">
1573
1576
  <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 12px;">
1574
1577
  <span style="font-size: 32px;">📊</span>
1575
1578
  <div style="flex: 1;">
@@ -1581,25 +1584,25 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
1581
1584
  📋 查看投票详情
1582
1585
  </div>
1583
1586
  </div>
1584
- `}return f}window.viewPollDetail=async f=>{try{const T=(await d.getPoll(f)).poll,m=T.options.reduce((M,V)=>M+V.votes.length,0),u=T.options.some(M=>M.votes.includes(D)),v=T.status==="ended"||T.endTime&&new Date(T.endTime)<new Date;let w="";T.options.forEach((M,V)=>{const F=m>0?(M.votes.length/m*100).toFixed(1):0,o=M.votes.includes(D);w+=`
1585
- <div class="poll-option" style="margin-bottom: 12px; padding: 12px; background: var(--bg-tertiary); border-radius: 8px; border: 2px solid ${o?"var(--primary)":"var(--border)"};">
1587
+ `}return T}window.viewPollDetail=async T=>{try{const F=(await a.getPoll(T)).poll,x=F.options.reduce((O,oe)=>O+oe.votes.length,0),f=F.options.some(O=>O.votes.includes(o)),M=F.status==="ended"||F.endTime&&new Date(F.endTime)<new Date;let A="";F.options.forEach((O,oe)=>{const K=x>0?(O.votes.length/x*100).toFixed(1):0,h=O.votes.includes(o);A+=`
1588
+ <div class="poll-option" style="margin-bottom: 12px; padding: 12px; background: var(--bg-tertiary); border-radius: 8px; border: 2px solid ${h?"var(--primary)":"var(--border)"};">
1586
1589
  <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
1587
1590
  <div style="display: flex; align-items: center; gap: 8px;">
1588
- <span style="font-weight: 500; color: var(--text-primary);">${M.text}</span>
1589
- ${o?'<span style="color: var(--primary); font-size: 12px;">✓ 已投票</span>':""}
1591
+ <span style="font-weight: 500; color: var(--text-primary);">${O.text}</span>
1592
+ ${h?'<span style="color: var(--primary); font-size: 12px;">✓ 已投票</span>':""}
1590
1593
  </div>
1591
- <span style="font-weight: 600; color: var(--primary);">${M.votes.length} 票 (${F}%)</span>
1594
+ <span style="font-weight: 600; color: var(--primary);">${O.votes.length} 票 (${K}%)</span>
1592
1595
  </div>
1593
1596
  <div style="height: 8px; background: var(--bg-secondary); border-radius: 4px; overflow: hidden;">
1594
- <div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${F}%; transition: width 0.3s;"></div>
1597
+ <div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${K}%; transition: width 0.3s;"></div>
1595
1598
  </div>
1596
- ${!T.anonymous&&M.votes.length>0?`
1599
+ ${!F.anonymous&&O.votes.length>0?`
1597
1600
  <div style="margin-top: 8px; font-size: 12px; color: var(--text-secondary);">
1598
- 投票者: ${M.voterNames?M.voterNames.join(", "):""}
1601
+ 投票者: ${O.voterNames?O.voterNames.join(", "):""}
1599
1602
  </div>
1600
1603
  `:""}
1601
1604
  </div>
1602
- `});const $=`
1605
+ `});const I=`
1603
1606
  <div id="pollDetailModal" class="modal" style="display: flex;">
1604
1607
  <div class="modal-content" style="max-width: 700px; max-height: 90vh; overflow-y: auto;">
1605
1608
  <div class="modal-header">
@@ -1608,37 +1611,37 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
1608
1611
  </div>
1609
1612
  <div class="modal-body" style="padding: 24px;">
1610
1613
  <div style="margin-bottom: 24px;">
1611
- <h2 style="margin: 0 0 12px 0; color: var(--text-primary);">${T.title}</h2>
1612
- ${T.description?`<p style="color: var(--text-secondary); margin: 0 0 16px 0;">${T.description}</p>`:""}
1614
+ <h2 style="margin: 0 0 12px 0; color: var(--text-primary);">${F.title}</h2>
1615
+ ${F.description?`<p style="color: var(--text-secondary); margin: 0 0 16px 0;">${F.description}</p>`:""}
1613
1616
 
1614
1617
  <div style="display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 16px;">
1615
1618
  <span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px; color: var(--text-secondary);">
1616
- ${T.allowMultiple?"✓ 多选投票":"○ 单选投票"}
1619
+ ${F.allowMultiple?"✓ 多选投票":"○ 单选投票"}
1617
1620
  </span>
1618
1621
  <span style="font-size: 13px; padding: 6px 12px; background: var(--bg-tertiary); border-radius: 14px; color: var(--text-secondary);">
1619
- ${T.anonymous?"🔒 匿名投票":"👤 实名投票"}
1622
+ ${F.anonymous?"🔒 匿名投票":"👤 实名投票"}
1620
1623
  </span>
1621
- ${v?'<span style="font-size: 13px; padding: 6px 12px; background: var(--danger); border-radius: 14px; color: white;">已结束</span>':'<span style="font-size: 13px; padding: 6px 12px; background: var(--success); border-radius: 14px; color: white;">进行中</span>'}
1624
+ ${M?'<span style="font-size: 13px; padding: 6px 12px; background: var(--danger); border-radius: 14px; color: white;">已结束</span>':'<span style="font-size: 13px; padding: 6px 12px; background: var(--success); border-radius: 14px; color: white;">进行中</span>'}
1622
1625
  </div>
1623
1626
 
1624
1627
  <div style="padding: 16px; background: var(--bg-secondary); border-radius: 12px; border-left: 4px solid var(--primary); margin-bottom: 24px;">
1625
1628
  <div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 16px;">
1626
1629
  <div>
1627
1630
  <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">创建者</div>
1628
- <div style="font-weight: 600; color: var(--text-primary);">👤 ${T.creatorName}</div>
1631
+ <div style="font-weight: 600; color: var(--text-primary);">👤 ${F.creatorName}</div>
1629
1632
  </div>
1630
1633
  <div>
1631
1634
  <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">总投票数</div>
1632
- <div style="font-weight: 600; color: var(--primary);">👥 ${m} 人</div>
1635
+ <div style="font-weight: 600; color: var(--primary);">👥 ${x} 人</div>
1633
1636
  </div>
1634
1637
  <div>
1635
1638
  <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">创建时间</div>
1636
- <div style="font-weight: 600; color: var(--text-primary);">⏰ ${new Date(T.createdAt).toLocaleString("zh-CN")}</div>
1639
+ <div style="font-weight: 600; color: var(--text-primary);">⏰ ${new Date(F.createdAt).toLocaleString("zh-CN")}</div>
1637
1640
  </div>
1638
- ${T.endTime?`
1641
+ ${F.endTime?`
1639
1642
  <div>
1640
1643
  <div style="font-size: 12px; color: var(--text-secondary); margin-bottom: 4px;">截止时间</div>
1641
- <div style="font-weight: 600; color: var(--text-primary);">⏰ ${new Date(T.endTime).toLocaleString("zh-CN")}</div>
1644
+ <div style="font-weight: 600; color: var(--text-primary);">⏰ ${new Date(F.endTime).toLocaleString("zh-CN")}</div>
1642
1645
  </div>
1643
1646
  `:""}
1644
1647
  </div>
@@ -1647,28 +1650,28 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
1647
1650
 
1648
1651
  <div style="margin-bottom: 24px;">
1649
1652
  <h3 style="margin-bottom: 16px; color: var(--text-primary);">投票选项</h3>
1650
- ${!v&&!u?`
1653
+ ${!M&&!f?`
1651
1654
  <form id="voteForm">
1652
- ${T.options.map((M,V)=>{const F=m>0?(M.votes.length/m*100).toFixed(1):0;return`
1655
+ ${F.options.map((O,oe)=>{const K=x>0?(O.votes.length/x*100).toFixed(1):0;return`
1653
1656
  <div class="poll-option" style="margin-bottom: 12px; padding: 12px; background: var(--bg-tertiary); border-radius: 8px; border: 2px solid var(--border); cursor: pointer;" onmouseover="this.style.borderColor='var(--primary)'" onmouseout="this.style.borderColor='var(--border)'">
1654
1657
  <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
1655
1658
  <div style="display: flex; align-items: center; gap: 8px;">
1656
- <input type="${T.allowMultiple?"checkbox":"radio"}" name="poll-option" value="${V}" style="width: 18px; height: 18px; cursor: pointer;">
1657
- <span style="font-weight: 500; color: var(--text-primary);">${M.text}</span>
1659
+ <input type="${F.allowMultiple?"checkbox":"radio"}" name="poll-option" value="${oe}" style="width: 18px; height: 18px; cursor: pointer;">
1660
+ <span style="font-weight: 500; color: var(--text-primary);">${O.text}</span>
1658
1661
  </div>
1659
- <span style="font-weight: 600; color: var(--primary);">${M.votes.length} 票 (${F}%)</span>
1662
+ <span style="font-weight: 600; color: var(--primary);">${O.votes.length} 票 (${K}%)</span>
1660
1663
  </div>
1661
1664
  <div style="height: 8px; background: var(--bg-secondary); border-radius: 4px; overflow: hidden;">
1662
- <div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${F}%; transition: width 0.3s;"></div>
1665
+ <div style="height: 100%; background: linear-gradient(90deg, var(--primary) 0%, var(--secondary) 100%); width: ${K}%; transition: width 0.3s;"></div>
1663
1666
  </div>
1664
1667
  </div>
1665
1668
  `}).join("")}
1666
1669
  <button type="submit" class="btn-primary" style="width: 100%; padding: 12px; margin-top: 16px;">提交投票</button>
1667
1670
  <div style="text-align: center; color: var(--warning); margin-top: 12px; font-size: 13px;">⚠️ 提交后不可修改</div>
1668
1671
  </form>
1669
- `:w}
1670
- ${u?'<div style="text-align: center; color: var(--success); margin-top: 16px; padding: 12px; background: rgba(34, 197, 94, 0.1); border-radius: 8px; font-weight: 600;">✓ 您已参与投票,投票后不可修改</div>':""}
1671
- ${v?'<div style="text-align: center; color: var(--text-secondary); margin-top: 16px; padding: 12px; background: var(--bg-tertiary); border-radius: 8px;">投票已结束</div>':""}
1672
+ `:A}
1673
+ ${f?'<div style="text-align: center; color: var(--success); margin-top: 16px; padding: 12px; background: rgba(34, 197, 94, 0.1); border-radius: 8px; font-weight: 600;">✓ 您已参与投票,投票后不可修改</div>':""}
1674
+ ${M?'<div style="text-align: center; color: var(--text-secondary); margin-top: 16px; padding: 12px; background: var(--bg-tertiary); border-radius: 8px;">投票已结束</div>':""}
1672
1675
  </div>
1673
1676
 
1674
1677
  <div style="display: flex; gap: 10px;">
@@ -1677,7 +1680,7 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
1677
1680
  </div>
1678
1681
  </div>
1679
1682
  </div>
1680
- `,C=document.getElementById("pollDetailModal");C&&C.remove(),document.body.insertAdjacentHTML("beforeend",$),document.getElementById("closePollDetailModal").addEventListener("click",()=>{document.getElementById("pollDetailModal").remove()}),document.getElementById("closePollDetailBtn").addEventListener("click",()=>{document.getElementById("pollDetailModal").remove()});const j=document.getElementById("voteForm");j&&!v&&!u&&j.addEventListener("submit",async M=>{M.preventDefault();const V=Array.from(document.querySelectorAll('input[name="poll-option"]:checked')).map(F=>parseInt(F.value));if(V.length===0){alert("请选择至少一个选项!");return}try{await d.vote(f,V),alert("投票成功!"),document.getElementById("pollDetailModal").remove();const F=document.querySelector(".nav-item.active");if(F&&F.dataset.view==="tasks"){const o=document.getElementById("contentArea");await K(o)}}catch(F){console.error("投票失败:",F),alert("投票失败:"+F.message)}})}catch(b){console.error("加载投票详情失败:",b),alert("加载投票详情失败:"+b.message)}},y.innerHTML=`
1683
+ `,z=document.getElementById("pollDetailModal");z&&z.remove(),document.body.insertAdjacentHTML("beforeend",I),document.getElementById("closePollDetailModal").addEventListener("click",()=>{document.getElementById("pollDetailModal").remove()}),document.getElementById("closePollDetailBtn").addEventListener("click",()=>{document.getElementById("pollDetailModal").remove()});const W=document.getElementById("voteForm");W&&!M&&!f&&W.addEventListener("submit",async O=>{O.preventDefault();const oe=Array.from(document.querySelectorAll('input[name="poll-option"]:checked')).map(K=>parseInt(K.value));if(oe.length===0){alert("请选择至少一个选项!");return}try{await a.vote(T,oe),alert("投票成功!"),document.getElementById("pollDetailModal").remove();const K=document.querySelector(".nav-item.active");if(K&&K.dataset.view==="tasks"){const h=document.getElementById("contentArea");await _(h)}}catch(K){console.error("投票失败:",K),alert("投票失败:"+K.message)}})}catch(E){console.error("加载投票详情失败:",E),alert("加载投票详情失败:"+E.message)}},n.innerHTML=`
1681
1684
  <div class="dashboard">
1682
1685
  <aside class="sidebar">
1683
1686
  <div class="sidebar-header">
@@ -1686,9 +1689,9 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
1686
1689
  </div>
1687
1690
 
1688
1691
  <div class="user-info">
1689
- <div class="avatar">${_.username[0].toUpperCase()}</div>
1692
+ <div class="avatar">${t.username[0].toUpperCase()}</div>
1690
1693
  <div>
1691
- <div class="username">${_.username}</div>
1694
+ <div class="username">${t.username}</div>
1692
1695
  <div class="user-role">普通用户</div>
1693
1696
  </div>
1694
1697
  </div>
@@ -1727,110 +1730,110 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
1727
1730
  <div id="contentArea"></div>
1728
1731
  </main>
1729
1732
  </div>
1730
- `,document.querySelectorAll(".nav-item").forEach(f=>{f.addEventListener("click",()=>{document.querySelectorAll(".nav-item").forEach(T=>T.classList.remove("active")),f.classList.add("active");const b=f.dataset.view;te(b)})}),document.getElementById("logoutBtn").addEventListener("click",()=>{z.logout()});async function te(f){const b=document.getElementById("contentArea");switch(f){case"groups":await ne(b);break;case"allgroups":await Q(b);break;case"tasks":await K(b);break;case"documents":await J(b);break;case"files":await X(b);break;case"chat":await pe(b);break;case"search":await ge(b);break;case"knowledge":await Z(b);break}}async function ne(f){R=(await d.getGroups()).groups,f.innerHTML=`
1733
+ `,document.querySelectorAll(".nav-item").forEach(T=>{T.addEventListener("click",()=>{document.querySelectorAll(".nav-item").forEach(F=>F.classList.remove("active")),T.classList.add("active");const E=T.dataset.view;$(E)})}),document.getElementById("logoutBtn").addEventListener("click",()=>{l.logout()});async function $(T){const E=document.getElementById("contentArea");switch(T){case"groups":await D(E);break;case"allgroups":await H(E);break;case"tasks":await _(E);break;case"documents":await R(E);break;case"files":await Z(E);break;case"chat":await U(E);break;case"search":await V(E);break;case"knowledge":await re(E);break}}async function D(T){S=(await a.getGroups()).groups,T.innerHTML=`
1731
1734
  <div class="view-header">
1732
1735
  <h2>我的群组</h2>
1733
1736
  </div>
1734
1737
  <div class="groups-grid" id="groupsList"></div>
1735
- `;const T=document.getElementById("groupsList");if(R.length===0){T.innerHTML='<div class="empty-state">您还没有加入任何群组<br>请前往"所有群组"查看并加入</div>';return}R.forEach(m=>{const u=document.createElement("div");u.className="group-card",u.innerHTML=`
1736
- <h3>${m.name}</h3>
1737
- <p>${m.description||"暂无描述"}</p>
1738
+ `;const F=document.getElementById("groupsList");if(S.length===0){F.innerHTML='<div class="empty-state">您还没有加入任何群组<br>请前往"所有群组"查看并加入</div>';return}S.forEach(x=>{const f=document.createElement("div");f.className="group-card",f.innerHTML=`
1739
+ <h3>${x.name}</h3>
1740
+ <p>${x.description||"暂无描述"}</p>
1738
1741
  <div class="group-stats">
1739
- <span>👥 ${m.members.length} 成员</span>
1740
- <span>📄 ${m.documents.length} 文档</span>
1741
- <span>📋 ${m.tasks.length} 任务</span>
1742
+ <span>👥 ${x.members.length} 成员</span>
1743
+ <span>📄 ${x.documents.length} 文档</span>
1744
+ <span>📋 ${x.tasks.length} 任务</span>
1742
1745
  </div>
1743
1746
  <div style="display: flex; gap: 10px; margin-top: 10px;">
1744
- <button class="btn-select" data-id="${m._id}">进入群组</button>
1745
- <button class="btn-secondary" data-id="${m._id}" data-action="leave">退出群组</button>
1747
+ <button class="btn-select" data-id="${x._id}">进入群组</button>
1748
+ <button class="btn-secondary" data-id="${x._id}" data-action="leave">退出群组</button>
1746
1749
  </div>
1747
- `,T.appendChild(u)}),document.querySelectorAll(".btn-select").forEach(m=>{m.addEventListener("click",()=>{p=R.find(u=>u._id===m.dataset.id),a.joinGroup(p._id),alert(`已进入群组: ${p.name}`)})}),document.querySelectorAll('[data-action="leave"]').forEach(m=>{m.addEventListener("click",async()=>{if(confirm("确定要退出该群组吗?"))try{await d.leaveGroup(m.dataset.id),alert("已退出群组"),await ne(f)}catch(u){alert("退出失败: "+u.message)}})})}async function Q(f){const b=await d.getAllGroups(),m=(await d.getGroups()).groups.map(v=>v._id);f.innerHTML=`
1750
+ `,F.appendChild(f)}),document.querySelectorAll(".btn-select").forEach(x=>{x.addEventListener("click",()=>{i=S.find(f=>f._id===x.dataset.id),e.joinGroup(i._id),alert(`已进入群组: ${i.name}`)})}),document.querySelectorAll('[data-action="leave"]').forEach(x=>{x.addEventListener("click",async()=>{if(confirm("确定要退出该群组吗?"))try{await a.leaveGroup(x.dataset.id),alert("已退出群组"),await D(T)}catch(f){alert("退出失败: "+f.message)}})})}async function H(T){const E=await a.getAllGroups(),x=(await a.getGroups()).groups.map(M=>M._id);T.innerHTML=`
1748
1751
  <div class="view-header">
1749
1752
  <h2>所有群组</h2>
1750
1753
  </div>
1751
1754
  <div class="groups-grid" id="allGroupsList"></div>
1752
- `;const u=document.getElementById("allGroupsList");b.groups.forEach(v=>{const w=m.includes(v._id),$=document.createElement("div");$.className="group-card",$.innerHTML=`
1753
- <h3>${v.name}</h3>
1754
- <p>${v.description||"暂无描述"}</p>
1755
+ `;const f=document.getElementById("allGroupsList");E.groups.forEach(M=>{const A=x.includes(M._id),I=document.createElement("div");I.className="group-card",I.innerHTML=`
1756
+ <h3>${M.name}</h3>
1757
+ <p>${M.description||"暂无描述"}</p>
1755
1758
  <div class="group-stats">
1756
- <span>👥 ${v.members.length} 成员</span>
1757
- <span>📄 ${v.documents.length} 文档</span>
1759
+ <span>👥 ${M.members.length} 成员</span>
1760
+ <span>📄 ${M.documents.length} 文档</span>
1758
1761
  </div>
1759
- ${w?'<div style="color: var(--success); margin-top: 10px;">✓ 已加入</div>':`<button class="btn-primary" data-id="${v._id}" data-action="join">加入群组</button>`}
1760
- `,u.appendChild($)}),document.querySelectorAll('[data-action="join"]').forEach(v=>{v.addEventListener("click",async()=>{try{await d.joinGroup(v.dataset.id),alert("加入成功!"),await Q(f)}catch(w){alert("加入失败: "+w.message)}})})}async function K(f){try{const b=await d.getMyTasks();let T=[];try{const v=(await d.getGroups()).groups;for(const w of v)try{const $=await d.getGroupPolls(w._id);$.polls&&Array.isArray($.polls)&&(T=T.concat($.polls.map(C=>({...C,groupName:w.name}))))}catch($){console.error(`获取群组 ${w.name} 的投票失败:`,$)}}catch(u){console.error("获取投票失败:",u)}f.innerHTML=`
1762
+ ${A?'<div style="color: var(--success); margin-top: 10px;">✓ 已加入</div>':`<button class="btn-primary" data-id="${M._id}" data-action="join">加入群组</button>`}
1763
+ `,f.appendChild(I)}),document.querySelectorAll('[data-action="join"]').forEach(M=>{M.addEventListener("click",async()=>{try{await a.joinGroup(M.dataset.id),alert("加入成功!"),await H(T)}catch(A){alert("加入失败: "+A.message)}})})}async function _(T){try{const E=await a.getMyTasks();let F=[];try{const M=(await a.getGroups()).groups;for(const A of M)try{const I=await a.getGroupPolls(A._id);I.polls&&Array.isArray(I.polls)&&(F=F.concat(I.polls.map(z=>({...z,groupName:A.name}))))}catch(I){console.error(`获取群组 ${A.name} 的投票失败:`,I)}}catch(f){console.error("获取投票失败:",f)}T.innerHTML=`
1761
1764
  <div class="view-header">
1762
1765
  <h2>我的任务</h2>
1763
1766
  </div>
1764
1767
  <div class="tasks-list" id="tasksList"></div>
1765
- `;const m=document.getElementById("tasksList");if(b.tasks.length===0&&T.length===0){m.innerHTML='<div class="empty-state">暂无任务</div>';return}b.tasks.forEach(u=>{const v=document.createElement("div");v.className=`task-card status-${u.status}`,v.innerHTML=`
1766
- <h3>${u.title}</h3>
1767
- <p>${u.description}</p>
1768
+ `;const x=document.getElementById("tasksList");if(E.tasks.length===0&&F.length===0){x.innerHTML='<div class="empty-state">暂无任务</div>';return}E.tasks.forEach(f=>{const M=document.createElement("div");M.className=`task-card status-${f.status}`,M.innerHTML=`
1769
+ <h3>${f.title}</h3>
1770
+ <p>${f.description}</p>
1768
1771
  <div class="task-meta">
1769
- <span class="status-badge">${re(u.status)}</span>
1770
- <span>群组: ${u.group.name}</span>
1771
- ${u.deadline?`<span>截止: ${new Date(u.deadline).toLocaleDateString()}</span>`:""}
1772
+ <span class="status-badge">${ne(f.status)}</span>
1773
+ <span>群组: ${f.group.name}</span>
1774
+ ${f.deadline?`<span>截止: ${new Date(f.deadline).toLocaleDateString()}</span>`:""}
1772
1775
  </div>
1773
- ${u.relatedDocument?`<a href="#" class="doc-link" data-id="${u.relatedDocument._id}">📄 查看相关文档</a>`:""}
1776
+ ${f.relatedDocument?`<a href="#" class="doc-link" data-id="${f.relatedDocument._id}">📄 查看相关文档</a>`:""}
1774
1777
  <div class="task-actions">
1775
- ${u.status==="pending"?`<button class="btn-primary btn-sm" data-id="${u._id}" data-action="start">开始任务</button>`:""}
1776
- ${u.status==="in_progress"?`<button class="btn-success btn-sm" data-id="${u._id}" data-action="complete">完成任务</button>`:""}
1778
+ ${f.status==="pending"?`<button class="btn-primary btn-sm" data-id="${f._id}" data-action="start">开始任务</button>`:""}
1779
+ ${f.status==="in_progress"?`<button class="btn-success btn-sm" data-id="${f._id}" data-action="complete">完成任务</button>`:""}
1777
1780
  </div>
1778
- `,m.appendChild(v)}),T.forEach(u=>{const v=u.options.reduce((j,M)=>j+M.votes.length,0),w=u.options.some(j=>j.votes.includes(D)),$=u.status==="ended"||u.endTime&&new Date(u.endTime)<new Date,C=document.createElement("div");C.className="task-card poll-task",C.style.cssText="background: linear-gradient(135deg, rgba(99, 102, 241, 0.05) 0%, rgba(168, 85, 247, 0.05) 100%); border-left: 4px solid var(--primary);",C.innerHTML=`
1781
+ `,x.appendChild(M)}),F.forEach(f=>{const M=f.options.reduce((W,O)=>W+O.votes.length,0),A=f.options.some(W=>W.votes.includes(o)),I=f.status==="ended"||f.endTime&&new Date(f.endTime)<new Date,z=document.createElement("div");z.className="task-card poll-task",z.style.cssText="background: linear-gradient(135deg, rgba(99, 102, 241, 0.05) 0%, rgba(168, 85, 247, 0.05) 100%); border-left: 4px solid var(--primary);",z.innerHTML=`
1779
1782
  <div style="display: flex; align-items: center; gap: 12px; margin-bottom: 12px;">
1780
1783
  <span style="font-size: 32px;">📊</span>
1781
- <h3 style="margin: 0;">${u.title}</h3>
1784
+ <h3 style="margin: 0;">${f.title}</h3>
1782
1785
  </div>
1783
- <p>${u.description||"暂无描述"}</p>
1786
+ <p>${f.description||"暂无描述"}</p>
1784
1787
  <div class="task-meta">
1785
- <span class="status-badge" style="background: ${$?"var(--danger)":"var(--success)"};">${$?"已结束":"进行中"}</span>
1786
- <span>群组: ${u.groupName}</span>
1787
- <span>👥 ${v} 人投票</span>
1788
- ${w?'<span style="color: var(--success);">✓ 已投票</span>':'<span style="color: var(--warning);">⏳ 待投票</span>'}
1788
+ <span class="status-badge" style="background: ${I?"var(--danger)":"var(--success)"};">${I?"已结束":"进行中"}</span>
1789
+ <span>群组: ${f.groupName}</span>
1790
+ <span>👥 ${M} 人投票</span>
1791
+ ${A?'<span style="color: var(--success);">✓ 已投票</span>':'<span style="color: var(--warning);">⏳ 待投票</span>'}
1789
1792
  </div>
1790
1793
  <div class="task-actions">
1791
- <button class="btn-primary btn-sm" data-poll-id="${u._id}" data-action="view-poll">查看详情</button>
1794
+ <button class="btn-primary btn-sm" data-poll-id="${f._id}" data-action="view-poll">查看详情</button>
1792
1795
  </div>
1793
- `,m.appendChild(C)}),document.querySelectorAll('[data-action="start"], [data-action="complete"]').forEach(u=>{u.addEventListener("click",async()=>{const v=u.dataset.id,$=u.dataset.action==="start"?"in_progress":"completed";try{await d.updateTaskStatus(v,$),await K(f)}catch(C){alert("操作失败: "+C.message)}})}),document.querySelectorAll('[data-action="view-poll"]').forEach(u=>{u.addEventListener("click",()=>{const v=u.dataset.pollId;window.viewPollDetail(v)})})}catch(b){console.error("获取任务失败:",b),f.innerHTML=`
1796
+ `,x.appendChild(z)}),document.querySelectorAll('[data-action="start"], [data-action="complete"]').forEach(f=>{f.addEventListener("click",async()=>{const M=f.dataset.id,I=f.dataset.action==="start"?"in_progress":"completed";try{await a.updateTaskStatus(M,I),await _(T)}catch(z){alert("操作失败: "+z.message)}})}),document.querySelectorAll('[data-action="view-poll"]').forEach(f=>{f.addEventListener("click",()=>{const M=f.dataset.pollId;window.viewPollDetail(M)})})}catch(E){console.error("获取任务失败:",E),T.innerHTML=`
1794
1797
  <div class="view-header">
1795
1798
  <h2>我的任务</h2>
1796
1799
  </div>
1797
- <div class="empty-state">加载任务失败: ${b.message}</div>
1798
- `}}async function J(f){if(!p){f.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}const b=await d.getDocuments(p._id);f.innerHTML=`
1800
+ <div class="empty-state">加载任务失败: ${E.message}</div>
1801
+ `}}async function R(T){if(!i){T.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}const E=await a.getDocuments(i._id);T.innerHTML=`
1799
1802
  <div class="view-header">
1800
- <h2>共享文档 - ${p.name}</h2>
1803
+ <h2>共享文档 - ${i.name}</h2>
1801
1804
  </div>
1802
1805
  <div class="documents-list" id="docsList"></div>
1803
- `;const T=document.getElementById("docsList");if(b.documents.length===0){T.innerHTML='<div class="empty-state">暂无文档</div>';return}b.documents.forEach(m=>{const u=document.createElement("div");u.className="document-card",u.innerHTML=`
1804
- <h3>📄 ${m.title}</h3>
1806
+ `;const F=document.getElementById("docsList");if(E.documents.length===0){F.innerHTML='<div class="empty-state">暂无文档</div>';return}E.documents.forEach(x=>{const f=document.createElement("div");f.className="document-card",f.innerHTML=`
1807
+ <h3>📄 ${x.title}</h3>
1805
1808
  <div class="doc-meta">
1806
- <span>创建者: ${m.creator.username}</span>
1807
- <span>${m.permission==="readonly"?"🔒 只读":"✏️ 可编辑"}</span>
1808
- <span>更新: ${new Date(m.updatedAt).toLocaleString()}</span>
1809
+ <span>创建者: ${x.creator.username}</span>
1810
+ <span>${x.permission==="readonly"?"🔒 只读":"✏️ 可编辑"}</span>
1811
+ <span>更新: ${new Date(x.updatedAt).toLocaleString()}</span>
1809
1812
  </div>
1810
- <button class="btn-edit" data-id="${m._id}">
1811
- ${m.permission==="readonly"?"查看":"编辑"}
1813
+ <button class="btn-edit" data-id="${x._id}">
1814
+ ${x.permission==="readonly"?"查看":"编辑"}
1812
1815
  </button>
1813
- `,T.appendChild(u)}),document.querySelectorAll(".btn-edit").forEach(m=>{m.addEventListener("click",()=>{de(f,m.dataset.id)})})}async function de(f,b){const m=(await d.getDocument(b)).document;f.innerHTML=`
1816
+ `,F.appendChild(f)}),document.querySelectorAll(".btn-edit").forEach(x=>{x.addEventListener("click",()=>{ae(T,x.dataset.id)})})}async function ae(T,E){const x=(await a.getDocument(E)).document;T.innerHTML=`
1814
1817
  <div class="view-header">
1815
1818
  <button class="btn-back" id="backBtn">← 返回</button>
1816
- <h2>${m.title}</h2>
1817
- <span class="doc-status">${m.permission==="readonly"?"🔒 只读模式":"✏️ 编辑模式"}</span>
1819
+ <h2>${x.title}</h2>
1820
+ <span class="doc-status">${x.permission==="readonly"?"🔒 只读模式":"✏️ 编辑模式"}</span>
1818
1821
  </div>
1819
1822
  <div class="editor-container">
1820
1823
  <div class="editor-toolbar">
1821
1824
  <div class="online-users" id="onlineUsers">
1822
- <span class="user-badge">👤 ${_.username}</span>
1825
+ <span class="user-badge">👤 ${t.username}</span>
1823
1826
  </div>
1824
- ${m.permission==="editable"?'<button class="btn-primary" id="saveBtn">保存</button>':""}
1827
+ ${x.permission==="editable"?'<button class="btn-primary" id="saveBtn">保存</button>':""}
1825
1828
  </div>
1826
- <div id="editor" ${m.permission==="readonly"?'class="readonly"':""}></div>
1829
+ <div id="editor" ${x.permission==="readonly"?'class="readonly"':""}></div>
1827
1830
  <div class="editor-footer">
1828
- <span>最后编辑: ${new Date(m.updatedAt).toLocaleString()}</span>
1831
+ <span>最后编辑: ${new Date(x.updatedAt).toLocaleString()}</span>
1829
1832
  </div>
1830
1833
  </div>
1831
- `;const u=new Quill("#editor",{theme:"snow",modules:{toolbar:m.permission==="readonly"?!1:[[{header:[1,2,3,!1]}],["bold","italic","underline","strike"],[{list:"ordered"},{list:"bullet"}],[{color:[]},{background:[]}],["link","image","code-block"],["clean"]]},readOnly:m.permission==="readonly"});if(u.root.innerHTML=m.content||"",m.permission==="editable"){let v,w;u.on("text-change",()=>{clearTimeout(v),clearTimeout(w),a.sendTyping(b,_.username,!0),v=setTimeout(()=>{a.sendTyping(b,_.username,!1)},1e3),w=setTimeout(async()=>{const $=u.root.innerHTML;try{await d.updateDocument(b,$)}catch(C){console.error("自动保存失败:",C)}},2e3)}),document.getElementById("saveBtn").addEventListener("click",async()=>{try{const $=u.root.innerHTML;await d.updateDocument(b,$),alert("保存成功!")}catch($){alert("保存失败: "+$.message)}})}a.on("document_update",v=>{if(v.documentId===b&&v.userId!==_.id){const w=u.getSelection();u.root.innerHTML=v.content,w&&u.setSelection(w)}}),a.on("typing",v=>{if(v.documentId===b&&v.userId!==_.id){const w=document.getElementById("onlineUsers");if(v.isTyping)w.innerHTML+=`<span class="user-badge typing" data-user="${v.userId}">✏️ ${v.username}</span>`;else{const $=w.querySelector(`[data-user="${v.userId}"]`);$&&$.remove()}}}),document.getElementById("backBtn").addEventListener("click",()=>{J(f)})}async function X(f){if(!p){f.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const b=await d.getGroupFiles(p._id);f.innerHTML=`
1834
+ `;const f=new Quill("#editor",{theme:"snow",modules:{toolbar:x.permission==="readonly"?!1:[[{header:[1,2,3,!1]}],["bold","italic","underline","strike"],[{list:"ordered"},{list:"bullet"}],[{color:[]},{background:[]}],["link","image","code-block"],["clean"]]},readOnly:x.permission==="readonly"});if(f.root.innerHTML=x.content||"",x.permission==="editable"){let M,A;f.on("text-change",()=>{clearTimeout(M),clearTimeout(A),e.sendTyping(E,t.username,!0),M=setTimeout(()=>{e.sendTyping(E,t.username,!1)},1e3),A=setTimeout(async()=>{const I=f.root.innerHTML;try{await a.updateDocument(E,I)}catch(z){console.error("自动保存失败:",z)}},2e3)}),document.getElementById("saveBtn").addEventListener("click",async()=>{try{const I=f.root.innerHTML;await a.updateDocument(E,I),alert("保存成功!")}catch(I){alert("保存失败: "+I.message)}})}e.on("document_update",M=>{if(M.documentId===E&&M.userId!==t.id){const A=f.getSelection();f.root.innerHTML=M.content,A&&f.setSelection(A)}}),e.on("typing",M=>{if(M.documentId===E&&M.userId!==t.id){const A=document.getElementById("onlineUsers");if(M.isTyping)A.innerHTML+=`<span class="user-badge typing" data-user="${M.userId}">✏️ ${M.username}</span>`;else{const I=A.querySelector(`[data-user="${M.userId}"]`);I&&I.remove()}}}),document.getElementById("backBtn").addEventListener("click",()=>{R(T)})}async function Z(T){if(!i){T.innerHTML='<div class="empty-state">请先选择一个群组</div>';return}try{const E=await a.getGroupFiles(i._id);T.innerHTML=`
1832
1835
  <div class="view-header">
1833
- <h2>文件共享 - ${p.name}</h2>
1836
+ <h2>文件共享 - ${i.name}</h2>
1834
1837
  <button class="btn-primary" id="uploadFileBtn">📤 上传文件</button>
1835
1838
  </div>
1836
1839
  <div class="files-list" id="filesList"></div>
@@ -1859,27 +1862,27 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
1859
1862
  </form>
1860
1863
  </div>
1861
1864
  </div>
1862
- `;const T=document.getElementById("filesList");!b.files||b.files.length===0?T.innerHTML='<div class="empty-state">暂无文件</div>':(b.files.forEach(m=>{const u=document.createElement("div");u.className="file-card";const v=le(m.mimetype),w=ce(m.size);u.innerHTML=`
1863
- <div class="file-icon">${v}</div>
1865
+ `;const F=document.getElementById("filesList");!E.files||E.files.length===0?F.innerHTML='<div class="empty-state">暂无文件</div>':(E.files.forEach(x=>{const f=document.createElement("div");f.className="file-card";const M=se(x.mimetype),A=le(x.size);f.innerHTML=`
1866
+ <div class="file-icon">${M}</div>
1864
1867
  <div class="file-info">
1865
- <h4>${m.originalName}</h4>
1868
+ <h4>${x.originalName}</h4>
1866
1869
  <div class="file-meta">
1867
- <span>上传者: ${m.uploader.username}</span>
1868
- <span>大小: ${w}</span>
1869
- <span>时间: ${new Date(m.createdAt).toLocaleString()}</span>
1870
+ <span>上传者: ${x.uploader.username}</span>
1871
+ <span>大小: ${A}</span>
1872
+ <span>时间: ${new Date(x.createdAt).toLocaleString()}</span>
1870
1873
  </div>
1871
- ${m.description?`<p class="file-description">${m.description}</p>`:""}
1874
+ ${x.description?`<p class="file-description">${x.description}</p>`:""}
1872
1875
  </div>
1873
1876
  <div class="file-actions">
1874
- <a href="${d.getFileDownloadUrl(m._id)}" class="btn-primary" download>下载</a>
1875
- ${String(m.uploader._id)===String(D)?`<button class="btn-danger" data-id="${m._id}" data-action="delete-file">删除</button>`:""}
1877
+ <a href="${a.getFileDownloadUrl(x._id)}" class="btn-primary" download>下载</a>
1878
+ ${String(x.uploader._id)===String(o)?`<button class="btn-danger" data-id="${x._id}" data-action="delete-file">删除</button>`:""}
1876
1879
  </div>
1877
- `,T.appendChild(u)}),document.querySelectorAll('[data-action="delete-file"]').forEach(m=>{m.addEventListener("click",async()=>{if(confirm("确定要删除这个文件吗?"))try{await d.deleteFile(m.dataset.id),alert("文件删除成功!"),await X(f)}catch(u){alert("删除失败: "+u.message)}})})),document.getElementById("uploadFileBtn").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.remove("hidden")}),document.getElementById("closeUploadModal").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset()}),document.getElementById("cancelUpload").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset()}),document.getElementById("uploadFileForm").addEventListener("submit",async m=>{m.preventDefault();const u=document.getElementById("fileInput"),v=document.getElementById("fileDescription").value;if(!u.files[0]){alert("请选择文件");return}try{await d.uploadFile(p._id,u.files[0],v),alert("文件上传成功!"),document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset(),await X(f)}catch(w){alert("上传失败: "+w.message)}})}catch(b){console.error("获取文件列表失败:",b),f.innerHTML=`
1880
+ `,F.appendChild(f)}),document.querySelectorAll('[data-action="delete-file"]').forEach(x=>{x.addEventListener("click",async()=>{if(confirm("确定要删除这个文件吗?"))try{await a.deleteFile(x.dataset.id),alert("文件删除成功!"),await Z(T)}catch(f){alert("删除失败: "+f.message)}})})),document.getElementById("uploadFileBtn").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.remove("hidden")}),document.getElementById("closeUploadModal").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset()}),document.getElementById("cancelUpload").addEventListener("click",()=>{document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset()}),document.getElementById("uploadFileForm").addEventListener("submit",async x=>{x.preventDefault();const f=document.getElementById("fileInput"),M=document.getElementById("fileDescription").value;if(!f.files[0]){alert("请选择文件");return}try{await a.uploadFile(i._id,f.files[0],M),alert("文件上传成功!"),document.getElementById("uploadFileModal").classList.add("hidden"),document.getElementById("uploadFileForm").reset(),await Z(T)}catch(A){alert("上传失败: "+A.message)}})}catch(E){console.error("获取文件列表失败:",E),T.innerHTML=`
1878
1881
  <div class="view-header">
1879
1882
  <h2>文件共享</h2>
1880
1883
  </div>
1881
- <div class="empty-state">加载文件失败: ${b.message}</div>
1882
- `}}function le(f){return f.startsWith("image/")?"🖼️":f==="application/pdf"?"📕":f.includes("word")||f.includes("document")?"📘":f.includes("excel")||f.includes("spreadsheet")?"📗":f.includes("zip")||f.includes("compressed")?"📦":"📄"}function ce(f){if(f===0)return"0 Bytes";const b=1024,T=["Bytes","KB","MB","GB"],m=Math.floor(Math.log(f)/Math.log(b));return Math.round(f/Math.pow(b,m)*100)/100+" "+T[m]}async function pe(f){if(!p){f.innerHTML=`
1884
+ <div class="empty-state">加载文件失败: ${E.message}</div>
1885
+ `}}function se(T){return T.startsWith("image/")?"🖼️":T==="application/pdf"?"📕":T.includes("word")||T.includes("document")?"📘":T.includes("excel")||T.includes("spreadsheet")?"📗":T.includes("zip")||T.includes("compressed")?"📦":"📄"}function le(T){if(T===0)return"0 Bytes";const E=1024,F=["Bytes","KB","MB","GB"],x=Math.floor(Math.log(T)/Math.log(E));return Math.round(T/Math.pow(E,x)*100)/100+" "+F[x]}async function U(T){if(!i){T.innerHTML=`
1883
1886
  <div class="empty-state" style="text-align: center; padding: 60px 20px; background: var(--bg-secondary); border-radius: 16px; border: 2px dashed var(--border);">
1884
1887
  <div style="font-size: 64px; margin-bottom: 20px;">💬</div>
1885
1888
  <h3 style="font-size: 24px; margin-bottom: 12px; color: var(--text-primary);">群聊</h3>
@@ -1888,15 +1891,15 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
1888
1891
  前往我的群组
1889
1892
  </button>
1890
1893
  </div>
1891
- `;return}try{let V=function(t,e,l="💬"){"Notification"in window&&Notification.permission==="granted"&&new Notification(t,{body:e,icon:"/icon.png",badge:"/icon.png",tag:"chat-message"})},n=function(){const t=document.getElementById("whiteboard");if(!t||t.dataset.initialized)return;t.dataset.initialized="true";const e=t.getContext("2d");t.width=t.offsetWidth,t.height=t.offsetHeight;let l=!1,c="pen",h="#667eea",E=3;document.querySelectorAll(".tool-btn").forEach(k=>{k.addEventListener("click",()=>{c=k.dataset.tool,document.querySelectorAll(".tool-btn").forEach(A=>{A.style.background="var(--bg-secondary)",A.style.color="var(--text-primary)",A.style.border="1px solid var(--border)"}),k.style.background="var(--primary)",k.style.color="white",k.style.border="none"})}),document.getElementById("colorPicker").addEventListener("change",k=>{h=k.target.value}),document.getElementById("brushSize").addEventListener("input",k=>{E=k.target.value}),document.getElementById("clearCanvas").addEventListener("click",()=>{confirm("确定要清空画布吗?")&&(e.clearRect(0,0,t.width,t.height),a.sendWhiteboardClear(p._id))}),document.getElementById("sendWhiteboardBtn").addEventListener("click",async()=>{try{const k=t.toDataURL("image/png"),A=document.createElement("canvas");A.width=t.width,A.height=t.height;const g=A.toDataURL("image/png");if(k===g){alert("画布是空的,请先绘制内容!");return}if(k.length*.75/1024/1024<1)a.sendChatMessage(p._id,_.username,`[白板作品]${k}`),alert("白板作品已发送到群聊!");else{const S=await fetch(k).then(q=>q.blob()),L=new FormData;L.append("file",S,`whiteboard-${Date.now()}.png`),L.append("groupId",p._id),L.append("description","协作白板作品");const H=localStorage.getItem("token"),P=await fetch("http://localhost:8765/api/files/upload",{method:"POST",headers:{Authorization:`Bearer ${H}`},body:L});if(P.ok){const G=`http://localhost:8765/api/files/${(await P.json()).file._id}/download?token=${H}`;a.sendChatMessage(p._id,_.username,`[白板作品]${G}`),alert("白板作品已发送到群聊!")}else throw new Error("上传失败")}}catch(k){console.error("发送白板失败:",k),alert("发送失败,请重试!")}}),t.addEventListener("mousedown",k=>{l=!0;const A=t.getBoundingClientRect(),g=k.clientX-A.left,I=k.clientY-A.top;e.beginPath(),e.moveTo(g,I)}),t.addEventListener("mousemove",k=>{if(!l)return;const A=t.getBoundingClientRect(),g=k.clientX-A.left,I=k.clientY-A.top;e.lineWidth=E,e.lineCap="round",c==="pen"?(e.strokeStyle=h,e.globalCompositeOperation="source-over"):c==="eraser"&&(e.globalCompositeOperation="destination-out"),e.lineTo(g,I),e.stroke(),a.sendWhiteboardDraw(p._id,{tool:c,color:h,size:E,x:g,y:I})}),t.addEventListener("mouseup",()=>{l=!1}),t.addEventListener("mouseleave",()=>{l=!1}),a.on("whiteboard_draw",k=>{k.groupId===p._id&&(e.lineWidth=k.size,e.lineCap="round",k.tool==="pen"?(e.strokeStyle=k.color,e.globalCompositeOperation="source-over"):k.tool==="eraser"&&(e.globalCompositeOperation="destination-out"),e.lineTo(k.x,k.y),e.stroke())}),a.on("whiteboard_clear",k=>{k.groupId===p._id&&e.clearRect(0,0,t.width,t.height)})};const T=(await d.getGroup(p._id)).group,m=!!T.mutedAll,u=(T.mutedUsers||[]).map(String).includes(String(D)),v=!m&&!u;f.innerHTML=`
1894
+ `;return}try{let h=function(d,s,c="💬"){"Notification"in window&&Notification.permission==="granted"&&new Notification(d,{body:s,icon:"/icon.png",badge:"/icon.png",tag:"chat-message"})},k=function(){const d=document.getElementById("whiteboard");if(!d||d.dataset.initialized)return;d.dataset.initialized="true";const s=d.getContext("2d");d.width=d.offsetWidth,d.height=d.offsetHeight;let c=!1,v="pen",w="#667eea",P=3;document.querySelectorAll(".tool-btn").forEach(p=>{p.addEventListener("click",()=>{v=p.dataset.tool,document.querySelectorAll(".tool-btn").forEach(L=>{L.style.background="var(--bg-secondary)",L.style.color="var(--text-primary)",L.style.border="1px solid var(--border)"}),p.style.background="var(--primary)",p.style.color="white",p.style.border="none"})}),document.getElementById("colorPicker").addEventListener("change",p=>{w=p.target.value}),document.getElementById("brushSize").addEventListener("input",p=>{P=p.target.value}),document.getElementById("clearCanvas").addEventListener("click",()=>{confirm("确定要清空画布吗?")&&(s.clearRect(0,0,d.width,d.height),e.sendWhiteboardClear(i._id))}),document.getElementById("sendWhiteboardBtn").addEventListener("click",async()=>{try{const p=d.toDataURL("image/png"),L=document.createElement("canvas");L.width=d.width,L.height=d.height;const j=L.toDataURL("image/png");if(p===j){alert("画布是空的,请先绘制内容!");return}if(p.length*.75/1024/1024<1)e.sendChatMessage(i._id,t.username,`[白板作品]${p}`),alert("白板作品已发送到群聊!");else{const G=await fetch(p).then(te=>te.blob()),q=new FormData;q.append("file",G,`whiteboard-${Date.now()}.png`),q.append("groupId",i._id),q.append("description","协作白板作品");const Y=localStorage.getItem("token"),X=await fetch("http://localhost:8765/api/files/upload",{method:"POST",headers:{Authorization:`Bearer ${Y}`},body:q});if(X.ok){const ie=`http://localhost:8765/api/files/${(await X.json()).file._id}/download?token=${Y}`;e.sendChatMessage(i._id,t.username,`[白板作品]${ie}`),alert("白板作品已发送到群聊!")}else throw new Error("上传失败")}}catch(p){console.error("发送白板失败:",p),alert("发送失败,请重试!")}}),d.addEventListener("mousedown",p=>{c=!0;const L=d.getBoundingClientRect(),j=p.clientX-L.left,C=p.clientY-L.top;s.beginPath(),s.moveTo(j,C)}),d.addEventListener("mousemove",p=>{if(!c)return;const L=d.getBoundingClientRect(),j=p.clientX-L.left,C=p.clientY-L.top;s.lineWidth=P,s.lineCap="round",v==="pen"?(s.strokeStyle=w,s.globalCompositeOperation="source-over"):v==="eraser"&&(s.globalCompositeOperation="destination-out"),s.lineTo(j,C),s.stroke(),e.sendWhiteboardDraw(i._id,{tool:v,color:w,size:P,x:j,y:C})}),d.addEventListener("mouseup",()=>{c=!1}),d.addEventListener("mouseleave",()=>{c=!1}),e.on("whiteboard_draw",p=>{p.groupId===i._id&&(s.lineWidth=p.size,s.lineCap="round",p.tool==="pen"?(s.strokeStyle=p.color,s.globalCompositeOperation="source-over"):p.tool==="eraser"&&(s.globalCompositeOperation="destination-out"),s.lineTo(p.x,p.y),s.stroke())}),e.on("whiteboard_clear",p=>{p.groupId===i._id&&s.clearRect(0,0,d.width,d.height)})};var E=h,F=k;const f=(await a.getGroup(i._id)).group,M=!!f.mutedAll,A=(f.mutedUsers||[]).map(String).includes(String(o)),I=!M&&!A;T.innerHTML=`
1892
1895
  <div class="view-header" style="background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 12px; margin-bottom: 20px;">
1893
1896
  <h2 style="margin: 0; display: flex; align-items: center; gap: 12px;">
1894
1897
  <span style="font-size: 32px;">💬</span>
1895
- <span>群聊 - ${p.name}</span>
1898
+ <span>群聊 - ${i.name}</span>
1896
1899
  </h2>
1897
- ${v?"":`
1900
+ ${I?"":`
1898
1901
  <div style="margin-top: 12px; padding: 12px; background: rgba(255,255,255,0.2); border-radius: 8px; font-size: 14px;">
1899
- ⚠️ ${m?"全体禁言中,无法发言":"你已被禁言"}
1902
+ ⚠️ ${M?"全体禁言中,无法发言":"你已被禁言"}
1900
1903
  </div>
1901
1904
  `}
1902
1905
  </div>
@@ -1919,9 +1922,9 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
1919
1922
  <div class="chat-container" style="display: flex; flex-direction: column; height: calc(100vh - 350px); background: var(--bg-secondary); border-radius: 12px; overflow: hidden;">
1920
1923
  <div class="messages" id="messages" style="flex: 1; overflow-y: auto; padding: 20px;"></div>
1921
1924
  <div class="chat-input" style="display: flex; gap: 10px; padding: 16px; background: var(--bg-tertiary); border-top: 1px solid var(--border);">
1922
- <button class="btn-emoji" id="emojiBtn" ${v?"":"disabled"} style="padding: 10px 16px; background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 8px; cursor: ${v?"pointer":"not-allowed"}; font-size: 20px;">😊</button>
1923
- <input type="text" id="messageInput" placeholder="${v?"输入消息...":m?"全体禁言中,无法发言":"你已被禁言"}" ${v?"":"disabled"} style="flex: 1; padding: 10px 16px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-primary);">
1924
- <button class="btn-primary" id="sendBtn" ${v?"":"disabled"} style="padding: 10px 24px; border-radius: 8px; cursor: ${v?"pointer":"not-allowed"};">发送</button>
1925
+ <button class="btn-emoji" id="emojiBtn" ${I?"":"disabled"} style="padding: 10px 16px; background: var(--bg-secondary); border: 1px solid var(--border); border-radius: 8px; cursor: ${I?"pointer":"not-allowed"}; font-size: 20px;">😊</button>
1926
+ <input type="text" id="messageInput" placeholder="${I?"输入消息...":M?"全体禁言中,无法发言":"你已被禁言"}" ${I?"":"disabled"} style="flex: 1; padding: 10px 16px; border: 1px solid var(--border); border-radius: 8px; background: var(--bg-primary);">
1927
+ <button class="btn-primary" id="sendBtn" ${I?"":"disabled"} style="padding: 10px 24px; border-radius: 8px; cursor: ${I?"pointer":"not-allowed"};">发送</button>
1925
1928
  </div>
1926
1929
  <emoji-picker id="emojiPicker" class="hidden" style="position: absolute; bottom: 80px; left: 20px; z-index: 1000;"></emoji-picker>
1927
1930
  </div>
@@ -1958,40 +1961,40 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
1958
1961
  </div>
1959
1962
  </div>
1960
1963
  </div>
1961
- `;const w=document.getElementById("messages"),$=document.getElementById("messageInput"),C=document.getElementById("sendBtn");try{const t=await d.getGroupMessages(p._id);t.messages&&Array.isArray(t.messages)&&(t.messages.length===0?w.innerHTML=`
1964
+ `;const z=document.getElementById("messages"),W=document.getElementById("messageInput"),O=document.getElementById("sendBtn");try{const d=await a.getGroupMessages(i._id);d.messages&&Array.isArray(d.messages)&&(d.messages.length===0?z.innerHTML=`
1962
1965
  <div style="text-align: center; padding: 40px; color: var(--text-tertiary);">
1963
1966
  <div style="font-size: 48px; margin-bottom: 16px;">💬</div>
1964
1967
  <p>还没有消息,开始聊天吧!</p>
1965
1968
  </div>
1966
- `:(t.messages.forEach(e=>{const l=document.createElement("div"),c=String(e.sender)===String(D)||e.username===_.username,h=se(e.content),E=e.content.startsWith("[白板作品]")||e.content.startsWith("[投票]"),k=e.content.startsWith("[白板作品]");l.className=`message ${c?"own":""}`,l.style.cssText=`
1969
+ `:(d.messages.forEach(s=>{const c=document.createElement("div"),v=String(s.sender)===String(o)||s.username===t.username,w=N(s.content),P=s.content.startsWith("[白板作品]")||s.content.startsWith("[投票]"),p=s.content.startsWith("[白板作品]");c.className=`message ${v?"own":""}`,c.style.cssText=`
1967
1970
  margin-bottom: 16px;
1968
1971
  display: flex;
1969
1972
  flex-direction: column;
1970
- align-items: ${c?"flex-end":"flex-start"};
1971
- `,l.innerHTML=`
1973
+ align-items: ${v?"flex-end":"flex-start"};
1974
+ `,c.innerHTML=`
1972
1975
  <div class="message-header" style="display: flex; gap: 8px; margin-bottom: 4px; font-size: 12px; color: var(--text-tertiary);">
1973
- <span class="message-user">${e.username}</span>
1974
- <span class="message-time">${new Date(e.timestamp).toLocaleTimeString("zh-CN")}</span>
1976
+ <span class="message-user">${s.username}</span>
1977
+ <span class="message-time">${new Date(s.timestamp).toLocaleTimeString("zh-CN")}</span>
1975
1978
  </div>
1976
- <div class="message-content" style="background: ${k||E?"transparent":c?"linear-gradient(135deg, #667eea 0%, #764ba2 100%)":"var(--bg-tertiary)"}; color: ${c&&!E?"white":"var(--text-primary)"}; padding: ${E?"0":"12px 16px"}; border-radius: 16px; max-width: ${E?"90%":"70%"}; word-wrap: break-word; box-shadow: ${c&&!E?"0 4px 12px rgba(102, 126, 234, 0.3)":"0 2px 8px rgba(0,0,0,0.05)"};">${h}</div>
1977
- `,w.appendChild(l)}),w.scrollTop=w.scrollHeight))}catch(t){console.error("加载历史消息失败:",t),w.innerHTML=`
1979
+ <div class="message-content" style="background: ${p||P?"transparent":v?"linear-gradient(135deg, #667eea 0%, #764ba2 100%)":"var(--bg-tertiary)"}; color: ${v&&!P?"white":"var(--text-primary)"}; padding: ${P?"0":"12px 16px"}; border-radius: 16px; max-width: ${P?"90%":"70%"}; word-wrap: break-word; box-shadow: ${v&&!P?"0 4px 12px rgba(102, 126, 234, 0.3)":"0 2px 8px rgba(0,0,0,0.05)"};">${w}</div>
1980
+ `,z.appendChild(c)}),z.scrollTop=z.scrollHeight))}catch(d){console.error("加载历史消息失败:",d),z.innerHTML=`
1978
1981
  <div style="text-align: center; padding: 40px; color: var(--danger);">
1979
1982
  <div style="font-size: 48px; margin-bottom: 16px;">⚠️</div>
1980
1983
  <p>加载历史消息失败</p>
1981
- <p style="font-size: 14px; color: var(--text-tertiary);">${t.message}</p>
1984
+ <p style="font-size: 14px; color: var(--text-tertiary);">${d.message}</p>
1982
1985
  </div>
1983
- `}const j=document.getElementById("emojiBtn"),M=document.getElementById("emojiPicker");v&&(j.addEventListener("click",()=>{M.classList.toggle("hidden")}),M.addEventListener("emoji-click",t=>{$.value+=t.detail.unicode,$.focus(),M.classList.add("hidden")}),document.addEventListener("click",t=>{!j.contains(t.target)&&!M.contains(t.target)&&M.classList.add("hidden")})),"Notification"in window&&Notification.permission==="default"&&Notification.requestPermission(),a.on("chat_message",t=>{if(t.groupId===p._id){const e=document.createElement("div"),l=String(t.userId)===String(D)||t.username===_.username;e.className=`message ${l?"own":""}`,e.style.cssText=`
1986
+ `}const oe=document.getElementById("emojiBtn"),K=document.getElementById("emojiPicker");I&&(oe.addEventListener("click",()=>{K.classList.toggle("hidden")}),K.addEventListener("emoji-click",d=>{W.value+=d.detail.unicode,W.focus(),K.classList.add("hidden")}),document.addEventListener("click",d=>{!oe.contains(d.target)&&!K.contains(d.target)&&K.classList.add("hidden")})),"Notification"in window&&Notification.permission==="default"&&Notification.requestPermission(),e.on("chat_message",d=>{if(d.groupId===i._id){const s=document.createElement("div"),c=String(d.userId)===String(o)||d.username===t.username;s.className=`message ${c?"own":""}`,s.style.cssText=`
1984
1987
  margin-bottom: 16px;
1985
1988
  display: flex;
1986
1989
  flex-direction: column;
1987
- align-items: ${l?"flex-end":"flex-start"};
1988
- `;const c=se(t.content),h=t.content.startsWith("[白板作品]")||t.content.startsWith("[投票]"),E=t.content.startsWith("[白板作品]");e.innerHTML=`
1990
+ align-items: ${c?"flex-end":"flex-start"};
1991
+ `;const v=N(d.content),w=d.content.startsWith("[白板作品]")||d.content.startsWith("[投票]"),P=d.content.startsWith("[白板作品]");s.innerHTML=`
1989
1992
  <div class="message-header" style="display: flex; gap: 8px; margin-bottom: 4px; font-size: 12px; color: var(--text-tertiary);">
1990
- <span class="message-user">${t.username}</span>
1991
- <span class="message-time">${new Date(t.timestamp).toLocaleTimeString("zh-CN")}</span>
1993
+ <span class="message-user">${d.username}</span>
1994
+ <span class="message-time">${new Date(d.timestamp).toLocaleTimeString("zh-CN")}</span>
1992
1995
  </div>
1993
- <div class="message-content" style="background: ${E||h?"transparent":l?"linear-gradient(135deg, #667eea 0%, #764ba2 100%)":"var(--bg-tertiary)"}; color: ${l&&!h?"white":"var(--text-primary)"}; padding: ${h?"0":"12px 16px"}; border-radius: 16px; max-width: ${h?"90%":"70%"}; word-wrap: break-word; box-shadow: ${l&&!h?"0 4px 12px rgba(102, 126, 234, 0.3)":"0 2px 8px rgba(0,0,0,0.05)"};">${c}</div>
1994
- `,w.appendChild(e),w.scrollTop=w.scrollHeight,l||V(`${t.username} 在 ${p.name}`,t.content.startsWith("[")?"发送了特殊消息":t.content)}}),a.on("chat_blocked",t=>{if(t.groupId===p._id){const e=document.createElement("div");e.style.cssText=`
1996
+ <div class="message-content" style="background: ${P||w?"transparent":c?"linear-gradient(135deg, #667eea 0%, #764ba2 100%)":"var(--bg-tertiary)"}; color: ${c&&!w?"white":"var(--text-primary)"}; padding: ${w?"0":"12px 16px"}; border-radius: 16px; max-width: ${w?"90%":"70%"}; word-wrap: break-word; box-shadow: ${c&&!w?"0 4px 12px rgba(102, 126, 234, 0.3)":"0 2px 8px rgba(0,0,0,0.05)"};">${v}</div>
1997
+ `,z.appendChild(s),z.scrollTop=z.scrollHeight,c||h(`${d.username} 在 ${i.name}`,d.content.startsWith("[")?"发送了特殊消息":d.content)}}),e.on("chat_blocked",d=>{if(d.groupId===i._id){const s=document.createElement("div");s.style.cssText=`
1995
1998
  text-align: center;
1996
1999
  padding: 12px;
1997
2000
  margin: 16px auto;
@@ -1999,7 +2002,7 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
1999
2002
  color: white;
2000
2003
  border-radius: 8px;
2001
2004
  max-width: 80%;
2002
- `,e.textContent=t.message||"消息发送失败",w.appendChild(e),w.scrollTop=w.scrollHeight}}),a.on("call_response",t=>{if(t.groupId===p._id){const e=document.createElement("div");e.style.cssText=`
2005
+ `,s.textContent=d.message||"消息发送失败",z.appendChild(s),z.scrollTop=z.scrollHeight}}),e.on("call_response",d=>{if(d.groupId===i._id){const s=document.createElement("div");s.style.cssText=`
2003
2006
  text-align: center;
2004
2007
  padding: 12px;
2005
2008
  margin: 16px auto;
@@ -2007,7 +2010,7 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
2007
2010
  color: white;
2008
2011
  border-radius: 8px;
2009
2012
  max-width: 80%;
2010
- `,e.textContent=`${t.username} 已响应点名`,w.appendChild(e),w.scrollTop=w.scrollHeight}});const F=()=>{if(!v){alert(m?"全体禁言中,无法发言":"你已被禁言");return}const t=$.value.trim();if(t)try{a.sendChatMessage(p._id,_.username,t),$.value=""}catch(e){console.error("发送消息失败:",e),alert("发送失败: "+e.message)}};v&&(C.addEventListener("click",F),$.addEventListener("keypress",t=>{t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),F())}));const o=document.querySelectorAll(".chat-tab"),i=document.querySelectorAll(".tab-content");o.forEach(t=>{t.addEventListener("click",()=>{const e=t.dataset.tab;o.forEach(l=>{l.dataset.tab===e?(l.style.background="var(--primary)",l.style.color="white"):(l.style.background="transparent",l.style.color="var(--text-primary)")}),i.forEach(l=>{l.dataset.content===e?l.style.display="block":l.style.display="none"}),e==="whiteboard"&&n()})});const s=document.getElementById("aiChat"),x=document.getElementById("aiInput"),B=document.getElementById("aiSendBtn"),r=async()=>{const t=x.value.trim();if(!t)return;const e=document.createElement("div");e.style.cssText=`
2013
+ `,s.textContent=`${d.username} 已响应点名`,z.appendChild(s),z.scrollTop=z.scrollHeight}});const m=()=>{if(!I){alert(M?"全体禁言中,无法发言":"你已被禁言");return}const d=W.value.trim();if(d)try{e.sendChatMessage(i._id,t.username,d),W.value=""}catch(s){console.error("发送消息失败:",s),alert("发送失败: "+s.message)}};I&&(O.addEventListener("click",m),W.addEventListener("keypress",d=>{d.key==="Enter"&&!d.shiftKey&&(d.preventDefault(),m())}));const u=document.querySelectorAll(".chat-tab"),b=document.querySelectorAll(".tab-content");u.forEach(d=>{d.addEventListener("click",()=>{const s=d.dataset.tab;u.forEach(c=>{c.dataset.tab===s?(c.style.background="var(--primary)",c.style.color="white"):(c.style.background="transparent",c.style.color="var(--text-primary)")}),b.forEach(c=>{c.dataset.content===s?c.style.display="block":c.style.display="none"}),s==="whiteboard"&&k()})});const B=document.getElementById("aiChat"),y=document.getElementById("aiInput"),g=document.getElementById("aiSendBtn"),r=async()=>{const d=y.value.trim();if(!d)return;const s=document.createElement("div");s.style.cssText=`
2011
2014
  background: var(--primary);
2012
2015
  color: white;
2013
2016
  padding: 12px 16px;
@@ -2016,7 +2019,7 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
2016
2019
  max-width: 80%;
2017
2020
  margin-left: auto;
2018
2021
  word-wrap: break-word;
2019
- `,e.textContent=t,s.appendChild(e),x.value="";const l=document.createElement("div");l.style.cssText=`
2022
+ `,s.textContent=d,B.appendChild(s),y.value="";const c=document.createElement("div");c.style.cssText=`
2020
2023
  background: var(--bg-tertiary);
2021
2024
  color: var(--text-secondary);
2022
2025
  padding: 12px 16px;
@@ -2024,7 +2027,7 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
2024
2027
  margin-bottom: 16px;
2025
2028
  max-width: 80%;
2026
2029
  font-style: italic;
2027
- `,l.textContent="🤔 思考中...",s.appendChild(l),s.scrollTop=s.scrollHeight;try{const c=localStorage.getItem("token"),E=await(await fetch("http://localhost:8765/api/ai/ask",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${c}`},body:JSON.stringify({question:t,groupId:p==null?void 0:p._id})})).json();l.remove();const k=document.createElement("div");k.style.cssText=`
2030
+ `,c.textContent="🤔 思考中...",B.appendChild(c),B.scrollTop=B.scrollHeight;try{const v=localStorage.getItem("token"),P=await(await fetch("http://localhost:8765/api/ai/ask",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${v}`},body:JSON.stringify({question:d,groupId:i==null?void 0:i._id})})).json();c.remove();const p=document.createElement("div");p.style.cssText=`
2028
2031
  background: var(--bg-tertiary);
2029
2032
  color: var(--text-primary);
2030
2033
  padding: 12px 16px;
@@ -2033,21 +2036,21 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
2033
2036
  max-width: 80%;
2034
2037
  line-height: 1.6;
2035
2038
  word-wrap: break-word;
2036
- `,k.textContent=E.answer||"抱歉,我无法回答这个问题。",s.appendChild(k),s.scrollTop=s.scrollHeight}catch(c){l.remove();const h=document.createElement("div");h.style.cssText=`
2039
+ `,p.textContent=P.answer||"抱歉,我无法回答这个问题。",B.appendChild(p),B.scrollTop=B.scrollHeight}catch(v){c.remove();const w=document.createElement("div");w.style.cssText=`
2037
2040
  background: var(--danger);
2038
2041
  color: white;
2039
2042
  padding: 12px 16px;
2040
2043
  border-radius: 12px;
2041
2044
  margin-bottom: 16px;
2042
2045
  max-width: 80%;
2043
- `,h.textContent="抱歉,发生了错误: "+c.message,s.appendChild(h),s.scrollTop=s.scrollHeight}};B.addEventListener("click",r),x.addEventListener("keypress",t=>{t.key==="Enter"&&!t.shiftKey&&(t.preventDefault(),r())})}catch(b){console.error("加载群聊失败:",b),f.innerHTML=`
2046
+ `,w.textContent="抱歉,发生了错误: "+v.message,B.appendChild(w),B.scrollTop=B.scrollHeight}};g.addEventListener("click",r),y.addEventListener("keypress",d=>{d.key==="Enter"&&!d.shiftKey&&(d.preventDefault(),r())})}catch(x){console.error("加载群聊失败:",x),T.innerHTML=`
2044
2047
  <div class="empty-state" style="text-align: center; padding: 60px 20px;">
2045
2048
  <div style="font-size: 64px; margin-bottom: 20px;">⚠️</div>
2046
2049
  <h3 style="font-size: 24px; margin-bottom: 12px; color: var(--danger);">加载失败</h3>
2047
- <p style="color: var(--text-secondary); margin-bottom: 24px;">${b.message}</p>
2050
+ <p style="color: var(--text-secondary); margin-bottom: 24px;">${x.message}</p>
2048
2051
  <button class="btn-primary" onclick="location.reload()">重新加载</button>
2049
2052
  </div>
2050
- `}}async function ge(f){f.innerHTML=`
2053
+ `}}async function V(T){T.innerHTML=`
2051
2054
  <div class="view-header">
2052
2055
  <h2>🔍 搜索</h2>
2053
2056
  </div>
@@ -2069,18 +2072,18 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
2069
2072
  </div>
2070
2073
  <div class="search-results" id="searchResults"></div>
2071
2074
  </div>
2072
- `;const b=document.getElementById("searchInput"),T=document.getElementById("searchBtn"),m=document.getElementById("searchResults"),u=async()=>{const v=b.value.trim();if(!v){m.innerHTML='<div class="empty-state">请输入搜索关键词</div>';return}const w={messages:document.getElementById("filterMessages").checked,documents:document.getElementById("filterDocuments").checked,tasks:document.getElementById("filterTasks").checked};m.innerHTML='<div class="loading">搜索中...</div>';try{const $=[];if(w.messages&&p)try{const C=await d.getGroupMessages(p._id);C.messages&&C.messages.filter(M=>M.content.toLowerCase().includes(v.toLowerCase())).forEach(M=>{$.push({type:"message",title:`消息 - ${M.username}`,content:M.content,time:M.timestamp,group:p.name})})}catch(C){console.error("搜索消息失败:",C)}if(w.documents)try{if(p){const C=await d.getDocuments(p._id);C.documents&&C.documents.filter(M=>M.title.toLowerCase().includes(v.toLowerCase())||M.content.toLowerCase().includes(v.toLowerCase())).forEach(M=>{$.push({type:"document",title:M.title,content:M.content.substring(0,200),time:M.updatedAt,id:M._id,group:p.name})})}}catch(C){console.error("搜索文档失败:",C)}if(w.tasks)try{const C=await d.getMyTasks();C.tasks&&C.tasks.filter(M=>M.title.toLowerCase().includes(v.toLowerCase())||M.description&&M.description.toLowerCase().includes(v.toLowerCase())).forEach(M=>{$.push({type:"task",title:M.title,content:M.description||"",time:M.updatedAt,id:M._id,status:M.status})})}catch(C){console.error("搜索任务失败:",C)}$.length===0?m.innerHTML='<div class="empty-state">未找到相关结果</div>':m.innerHTML=$.map(C=>`
2075
+ `;const E=document.getElementById("searchInput"),F=document.getElementById("searchBtn"),x=document.getElementById("searchResults"),f=async()=>{const M=E.value.trim();if(!M){x.innerHTML='<div class="empty-state">请输入搜索关键词</div>';return}const A={messages:document.getElementById("filterMessages").checked,documents:document.getElementById("filterDocuments").checked,tasks:document.getElementById("filterTasks").checked};x.innerHTML='<div class="loading">搜索中...</div>';try{const I=[];if(A.messages&&i)try{const z=await a.getGroupMessages(i._id);z.messages&&z.messages.filter(O=>O.content.toLowerCase().includes(M.toLowerCase())).forEach(O=>{I.push({type:"message",title:`消息 - ${O.username}`,content:O.content,time:O.timestamp,group:i.name})})}catch(z){console.error("搜索消息失败:",z)}if(A.documents)try{if(i){const z=await a.getDocuments(i._id);z.documents&&z.documents.filter(O=>O.title.toLowerCase().includes(M.toLowerCase())||O.content.toLowerCase().includes(M.toLowerCase())).forEach(O=>{I.push({type:"document",title:O.title,content:O.content.substring(0,200),time:O.updatedAt,id:O._id,group:i.name})})}}catch(z){console.error("搜索文档失败:",z)}if(A.tasks)try{const z=await a.getMyTasks();z.tasks&&z.tasks.filter(O=>O.title.toLowerCase().includes(M.toLowerCase())||O.description&&O.description.toLowerCase().includes(M.toLowerCase())).forEach(O=>{I.push({type:"task",title:O.title,content:O.description||"",time:O.updatedAt,id:O._id,status:O.status})})}catch(z){console.error("搜索任务失败:",z)}I.length===0?x.innerHTML='<div class="empty-state">未找到相关结果</div>':x.innerHTML=I.map(z=>`
2073
2076
  <div class="search-result-item">
2074
2077
  <div class="result-header">
2075
- <span class="result-type">${{message:"💬",document:"📄",task:"📋"}[C.type]} ${C.type==="message"?"消息":C.type==="document"?"文档":"任务"}</span>
2076
- <span class="result-time">${new Date(C.time).toLocaleString()}</span>
2078
+ <span class="result-type">${{message:"💬",document:"📄",task:"📋"}[z.type]} ${z.type==="message"?"消息":z.type==="document"?"文档":"任务"}</span>
2079
+ <span class="result-time">${new Date(z.time).toLocaleString()}</span>
2077
2080
  </div>
2078
- <h4>${ie(C.title,v)}</h4>
2079
- <p>${ie(C.content,v)}</p>
2080
- ${C.group?`<span class="result-group">群组: ${C.group}</span>`:""}
2081
- ${C.status?`<span class="result-status">状态: ${re(C.status)}</span>`:""}
2081
+ <h4>${Q(z.title,M)}</h4>
2082
+ <p>${Q(z.content,M)}</p>
2083
+ ${z.group?`<span class="result-group">群组: ${z.group}</span>`:""}
2084
+ ${z.status?`<span class="result-status">状态: ${ne(z.status)}</span>`:""}
2082
2085
  </div>
2083
- `).join("")}catch($){m.innerHTML=`<div class="empty-state">搜索失败: ${$.message}</div>`}};T.addEventListener("click",u),b.addEventListener("keypress",v=>{v.key==="Enter"&&u()})}function ie(f,b){if(!b)return f;const T=new RegExp(`(${b})`,"gi");return f.replace(T,"<mark>$1</mark>")}function re(f){return{pending:"待处理",in_progress:"进行中",completed:"已完成",terminated:"已终止"}[f]||f}async function Z(f){if(!p){f.innerHTML=`
2086
+ `).join("")}catch(I){x.innerHTML=`<div class="empty-state">搜索失败: ${I.message}</div>`}};F.addEventListener("click",f),E.addEventListener("keypress",M=>{M.key==="Enter"&&f()})}function Q(T,E){if(!E)return T;const F=new RegExp(`(${E})`,"gi");return T.replace(F,"<mark>$1</mark>")}function ne(T){return{pending:"待处理",in_progress:"进行中",completed:"已完成",terminated:"已终止"}[T]||T}async function re(T){if(!i){T.innerHTML=`
2084
2087
  <div class="empty-state" style="text-align: center; padding: 60px 20px; background: var(--bg-secondary); border-radius: 16px; border: 2px dashed var(--border);">
2085
2088
  <div style="font-size: 64px; margin-bottom: 20px;">📚</div>
2086
2089
  <h3 style="font-size: 24px; margin-bottom: 12px; color: var(--text-primary);">知识库</h3>
@@ -2089,9 +2092,9 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
2089
2092
  前往我的群组
2090
2093
  </button>
2091
2094
  </div>
2092
- `;return}try{const b=localStorage.getItem("token"),T=await fetch(`http://localhost:8765/api/knowledge/group/${p._id}`,{headers:{Authorization:`Bearer ${b}`}});if(!T.ok)throw new Error(`HTTP ${T.status}: ${T.statusText}`);const m=await T.json();let u=[];Array.isArray(m)?u=m:m.data&&Array.isArray(m.data)?u=m.data:m.data&&m.data.knowledgeList&&Array.isArray(m.data.knowledgeList)?u=m.data.knowledgeList:m.items&&Array.isArray(m.items)?u=m.items:m.knowledge&&Array.isArray(m.knowledge)&&(u=m.knowledge),f.innerHTML=`
2095
+ `;return}try{const E=localStorage.getItem("token"),F=await fetch(`http://localhost:8765/api/knowledge/group/${i._id}`,{headers:{Authorization:`Bearer ${E}`}});if(!F.ok)throw new Error(`HTTP ${F.status}: ${F.statusText}`);const x=await F.json();let f=[];Array.isArray(x)?f=x:x.data&&Array.isArray(x.data)?f=x.data:x.data&&x.data.knowledgeList&&Array.isArray(x.data.knowledgeList)?f=x.data.knowledgeList:x.items&&Array.isArray(x.items)?f=x.items:x.knowledge&&Array.isArray(x.knowledge)&&(f=x.knowledge),T.innerHTML=`
2093
2096
  <div class="view-header">
2094
- <h2>📚 知识库 - ${p.name}</h2>
2097
+ <h2>📚 知识库 - ${i.name}</h2>
2095
2098
  <button class="btn-primary" id="createKnowledgeBtn">📝 创建知识条目</button>
2096
2099
  </div>
2097
2100
  <div class="knowledge-grid" id="knowledgeList" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(350px, 1fr)); gap: 20px; padding: 20px;"></div>
@@ -2133,31 +2136,31 @@ import"emoji-picker-element";(function(){const a=document.createElement("link").
2133
2136
  </form>
2134
2137
  </div>
2135
2138
  </div>
2136
- `;const v=document.getElementById("knowledgeList");u.length===0?v.innerHTML='<div class="empty-state" style="grid-column: 1/-1;">暂无知识条目</div>':(u.forEach(w=>{var C,j;const $=document.createElement("div");$.className="knowledge-card",$.style.cssText="background: var(--bg-secondary); padding: 20px; border-radius: 12px; border: 1px solid var(--border); transition: transform 0.2s, box-shadow 0.2s; position: relative;",$.innerHTML=`
2137
- ${w.isShared?'<div style="position: absolute; top: 15px; right: 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 4px 10px; border-radius: 12px; font-size: 11px; font-weight: 600; display: flex; align-items: center; gap: 4px;"><span>🌐</span><span>已共享</span></div>':""}
2138
- <h3 style="margin: 0 0 10px 0; font-size: 18px; ${w.isShared?"padding-right: 80px;":""}">${w.title}</h3>
2139
- <p style="color: var(--text-secondary); margin: 0 0 15px 0; line-height: 1.6;">${w.content.substring(0,150)}${w.content.length>150?"...":""}</p>
2139
+ `;const M=document.getElementById("knowledgeList");f.length===0?M.innerHTML='<div class="empty-state" style="grid-column: 1/-1;">暂无知识条目</div>':(f.forEach(A=>{var z,W;const I=document.createElement("div");I.className="knowledge-card",I.style.cssText="background: var(--bg-secondary); padding: 20px; border-radius: 12px; border: 1px solid var(--border); transition: transform 0.2s, box-shadow 0.2s; position: relative;",I.innerHTML=`
2140
+ ${A.isShared?'<div style="position: absolute; top: 15px; right: 15px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 4px 10px; border-radius: 12px; font-size: 11px; font-weight: 600; display: flex; align-items: center; gap: 4px;"><span>🌐</span><span>已共享</span></div>':""}
2141
+ <h3 style="margin: 0 0 10px 0; font-size: 18px; ${A.isShared?"padding-right: 80px;":""}">${A.title}</h3>
2142
+ <p style="color: var(--text-secondary); margin: 0 0 15px 0; line-height: 1.6;">${A.content.substring(0,150)}${A.content.length>150?"...":""}</p>
2140
2143
  <div class="knowledge-meta" style="font-size: 12px; color: var(--text-tertiary); margin-bottom: 10px;">
2141
- <span>👤 ${((C=w.author)==null?void 0:C.username)||((j=w.creator)==null?void 0:j.username)||"未知"}</span>
2142
- <span style="margin-left: 15px;">📅 ${new Date(w.createdAt).toLocaleDateString()}</span>
2144
+ <span>👤 ${((z=A.author)==null?void 0:z.username)||((W=A.creator)==null?void 0:W.username)||"未知"}</span>
2145
+ <span style="margin-left: 15px;">📅 ${new Date(A.createdAt).toLocaleDateString()}</span>
2143
2146
  </div>
2144
- ${w.tags&&w.tags.length>0?`
2147
+ ${A.tags&&A.tags.length>0?`
2145
2148
  <div class="tags" style="display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 15px;">
2146
- ${w.tags.map(M=>`<span class="tag" style="background: var(--primary); color: white; padding: 4px 10px; border-radius: 12px; font-size: 12px;">${M}</span>`).join("")}
2149
+ ${A.tags.map(O=>`<span class="tag" style="background: var(--primary); color: white; padding: 4px 10px; border-radius: 12px; font-size: 12px;">${O}</span>`).join("")}
2147
2150
  </div>
2148
2151
  `:""}
2149
2152
  <div style="display: flex; gap: 10px;">
2150
- <button class="btn-secondary btn-sm" data-id="${w._id}" data-action="edit" style="flex: 1;">✏️ 编辑</button>
2151
- <button class="btn-danger btn-sm" data-id="${w._id}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
2153
+ <button class="btn-secondary btn-sm" data-id="${A._id}" data-action="edit" style="flex: 1;">✏️ 编辑</button>
2154
+ <button class="btn-danger btn-sm" data-id="${A._id}" data-action="delete" style="flex: 1;">🗑️ 删除</button>
2152
2155
  </div>
2153
- `,$.onmouseenter=()=>{$.style.transform="translateY(-4px)",$.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},$.onmouseleave=()=>{$.style.transform="translateY(0)",$.style.boxShadow="none"},v.appendChild($)}),document.querySelectorAll('[data-action="edit"]').forEach(w=>{w.addEventListener("click",async()=>{var C;const $=u.find(j=>j._id===w.dataset.id);document.getElementById("modalTitle").textContent="编辑知识条目",document.querySelector('[name="title"]').value=$.title,document.querySelector('[name="content"]').value=$.content,document.querySelector('[name="tags"]').value=((C=$.tags)==null?void 0:C.join(", "))||"",document.getElementById("isSharedCheckbox").checked=$.isShared||!1,document.getElementById("knowledgeForm").dataset.editId=$._id,document.getElementById("knowledgeModal").classList.remove("hidden")})}),document.querySelectorAll('[data-action="delete"]').forEach(w=>{w.addEventListener("click",async()=>{if(confirm("确定要删除这个知识条目吗?"))try{await fetch(`http://localhost:8765/api/knowledge/${w.dataset.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${b}`}}),alert("删除成功!"),await Z(f)}catch($){alert("删除失败: "+$.message)}})})),document.getElementById("createKnowledgeBtn").addEventListener("click",()=>{document.getElementById("modalTitle").textContent="创建知识条目",document.getElementById("knowledgeForm").reset(),delete document.getElementById("knowledgeForm").dataset.editId,document.getElementById("knowledgeModal").classList.remove("hidden")}),document.getElementById("closeKnowledgeModal").addEventListener("click",()=>{document.getElementById("knowledgeModal").classList.add("hidden")}),document.getElementById("cancelKnowledgeModal").addEventListener("click",()=>{document.getElementById("knowledgeModal").classList.add("hidden")}),document.getElementById("knowledgeForm").addEventListener("submit",async w=>{w.preventDefault();const $=new FormData(w.target),C={title:$.get("title"),content:$.get("content"),tags:$.get("tags").split(",").map(j=>j.trim()).filter(j=>j),groupId:p._id,isShared:document.getElementById("isSharedCheckbox").checked};try{const j=w.target.dataset.editId,M=j?`http://localhost:8765/api/knowledge/${j}`:"http://localhost:8765/api/knowledge";if(!(await fetch(M,{method:j?"PUT":"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${b}`},body:JSON.stringify(C)})).ok)throw new Error("操作失败");alert(j?"更新成功!":"创建成功!"),document.getElementById("knowledgeModal").classList.add("hidden"),await Z(f)}catch(j){alert("操作失败: "+j.message)}})}catch(b){console.error("加载知识库失败:",b),f.innerHTML=`
2156
+ `,I.onmouseenter=()=>{I.style.transform="translateY(-4px)",I.style.boxShadow="0 8px 16px rgba(0,0,0,0.1)"},I.onmouseleave=()=>{I.style.transform="translateY(0)",I.style.boxShadow="none"},M.appendChild(I)}),document.querySelectorAll('[data-action="edit"]').forEach(A=>{A.addEventListener("click",async()=>{var z;const I=f.find(W=>W._id===A.dataset.id);document.getElementById("modalTitle").textContent="编辑知识条目",document.querySelector('[name="title"]').value=I.title,document.querySelector('[name="content"]').value=I.content,document.querySelector('[name="tags"]').value=((z=I.tags)==null?void 0:z.join(", "))||"",document.getElementById("isSharedCheckbox").checked=I.isShared||!1,document.getElementById("knowledgeForm").dataset.editId=I._id,document.getElementById("knowledgeModal").classList.remove("hidden")})}),document.querySelectorAll('[data-action="delete"]').forEach(A=>{A.addEventListener("click",async()=>{if(confirm("确定要删除这个知识条目吗?"))try{await fetch(`http://localhost:8765/api/knowledge/${A.dataset.id}`,{method:"DELETE",headers:{Authorization:`Bearer ${E}`}}),alert("删除成功!"),await re(T)}catch(I){alert("删除失败: "+I.message)}})})),document.getElementById("createKnowledgeBtn").addEventListener("click",()=>{document.getElementById("modalTitle").textContent="创建知识条目",document.getElementById("knowledgeForm").reset(),delete document.getElementById("knowledgeForm").dataset.editId,document.getElementById("knowledgeModal").classList.remove("hidden")}),document.getElementById("closeKnowledgeModal").addEventListener("click",()=>{document.getElementById("knowledgeModal").classList.add("hidden")}),document.getElementById("cancelKnowledgeModal").addEventListener("click",()=>{document.getElementById("knowledgeModal").classList.add("hidden")}),document.getElementById("knowledgeForm").addEventListener("submit",async A=>{A.preventDefault();const I=new FormData(A.target),z={title:I.get("title"),content:I.get("content"),tags:I.get("tags").split(",").map(W=>W.trim()).filter(W=>W),groupId:i._id,isShared:document.getElementById("isSharedCheckbox").checked};try{const W=A.target.dataset.editId,O=W?`http://localhost:8765/api/knowledge/${W}`:"http://localhost:8765/api/knowledge";if(!(await fetch(O,{method:W?"PUT":"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${E}`},body:JSON.stringify(z)})).ok)throw new Error("操作失败");alert(W?"更新成功!":"创建成功!"),document.getElementById("knowledgeModal").classList.add("hidden"),await re(T)}catch(W){alert("操作失败: "+W.message)}})}catch(E){console.error("加载知识库失败:",E),T.innerHTML=`
2154
2157
  <div class="view-header">
2155
- <h2>📚 知识库 - ${p.name}</h2>
2158
+ <h2>📚 知识库 - ${i.name}</h2>
2156
2159
  </div>
2157
2160
  <div class="empty-state" style="text-align: center; padding: 40px 20px;">
2158
2161
  <div style="font-size: 48px; margin-bottom: 16px;">⚠️</div>
2159
2162
  <h3 style="margin-bottom: 8px; color: var(--danger);">加载失败</h3>
2160
- <p style="color: var(--text-secondary); margin-bottom: 16px;">${b.message}</p>
2163
+ <p style="color: var(--text-secondary); margin-bottom: 16px;">${E.message}</p>
2161
2164
  <button class="btn-primary" onclick="location.reload()">重新加载</button>
2162
2165
  </div>
2163
- `}}te("groups")}class Le{constructor(){this.authService=new oe,this.wsService=new he,this.currentUser=null,this.init()}async init(){const a=localStorage.getItem("token");if(a)try{this.currentUser=await this.authService.getCurrentUser(),this.wsService.connect(a),this.renderDashboard()}catch(y){console.error("认证失败:",y),this.renderLogin()}else this.renderLogin()}renderLogin(){fe(async(a,y)=>{this.currentUser=a,localStorage.setItem("token",y),this.wsService.connect(y),this.renderDashboard()})}renderDashboard(){this.currentUser.role==="admin"?we(this.currentUser,this.wsService):Ee(this.currentUser,this.wsService)}}new Le;
2166
+ `}}$("groups")}class mn{constructor(){this.authService=new Be,this.wsService=new Ct,this.currentUser=null,this.init()}async init(){const e=localStorage.getItem("token");if(e)try{this.currentUser=await this.authService.getCurrentUser(),this.wsService.connect(e),this.renderDashboard()}catch(n){console.error("认证失败:",n),this.renderLogin()}else this.renderLogin()}renderLogin(){zt(async(e,n)=>{this.currentUser=e,localStorage.setItem("token",n),this.wsService.connect(n),this.renderDashboard()})}renderDashboard(){this.currentUser.role==="admin"?pn(this.currentUser,this.wsService):gn(this.currentUser,this.wsService)}}new mn;