debug-employee-projects.html 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>调试员工项目数据</title>
  7. <script src="https://unpkg.com/parse@4.2.0/dist/parse.min.js"></script>
  8. <style>
  9. body {
  10. font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
  11. max-width: 1200px;
  12. margin: 20px auto;
  13. padding: 20px;
  14. background: #f5f5f5;
  15. }
  16. .card {
  17. background: white;
  18. border-radius: 8px;
  19. padding: 20px;
  20. margin-bottom: 20px;
  21. box-shadow: 0 2px 4px rgba(0,0,0,0.1);
  22. }
  23. h1, h2 { color: #333; }
  24. button {
  25. background: #667eea;
  26. color: white;
  27. border: none;
  28. padding: 12px 24px;
  29. border-radius: 6px;
  30. cursor: pointer;
  31. font-size: 16px;
  32. margin: 5px;
  33. }
  34. button:hover { background: #5568d3; }
  35. button:disabled { background: #ccc; cursor: not-allowed; }
  36. .log {
  37. background: #1e1e1e;
  38. color: #d4d4d4;
  39. padding: 15px;
  40. border-radius: 6px;
  41. font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
  42. font-size: 13px;
  43. max-height: 600px;
  44. overflow-y: auto;
  45. white-space: pre-wrap;
  46. word-break: break-all;
  47. }
  48. .success { color: #4ade80; }
  49. .error { color: #f87171; }
  50. .warning { color: #fbbf24; }
  51. .info { color: #60a5fa; }
  52. select, input {
  53. padding: 8px 12px;
  54. border: 1px solid #ddd;
  55. border-radius: 4px;
  56. font-size: 14px;
  57. margin: 5px;
  58. }
  59. .employee-list {
  60. display: grid;
  61. grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  62. gap: 10px;
  63. margin-top: 10px;
  64. }
  65. .employee-card {
  66. padding: 10px;
  67. background: #f9fafb;
  68. border: 1px solid #e5e7eb;
  69. border-radius: 4px;
  70. cursor: pointer;
  71. transition: all 0.2s;
  72. }
  73. .employee-card:hover {
  74. background: #ede9fe;
  75. border-color: #667eea;
  76. }
  77. .employee-card.selected {
  78. background: #ddd6fe;
  79. border-color: #667eea;
  80. font-weight: 600;
  81. }
  82. </style>
  83. </head>
  84. <body>
  85. <h1>🔍 员工项目数据调试工具</h1>
  86. <div class="card">
  87. <h2>1️⃣ Parse 初始化</h2>
  88. <button onclick="initParse()">初始化 Parse</button>
  89. <div id="initStatus"></div>
  90. </div>
  91. <div class="card">
  92. <h2>2️⃣ 选择员工</h2>
  93. <button onclick="loadEmployees()">加载员工列表</button>
  94. <div id="employeeList" class="employee-list"></div>
  95. <div id="selectedEmployee"></div>
  96. </div>
  97. <div class="card">
  98. <h2>3️⃣ 查询员工项目</h2>
  99. <button onclick="queryEmployeeProjects()" id="queryBtn" disabled>查询选中员工的项目</button>
  100. <button onclick="queryAllProjects()">查询所有项目(验证数据结构)</button>
  101. </div>
  102. <div class="card">
  103. <h2>📋 调试日志</h2>
  104. <button onclick="clearLog()">清空日志</button>
  105. <div id="log" class="log">等待操作...</div>
  106. </div>
  107. <script>
  108. let Parse = window.Parse;
  109. let logEl = document.getElementById('log');
  110. let selectedEmployeeId = null;
  111. let companyId = null;
  112. function log(message, type = 'info') {
  113. const colors = {
  114. success: '#4ade80',
  115. error: '#f87171',
  116. warning: '#fbbf24',
  117. info: '#60a5fa'
  118. };
  119. const timestamp = new Date().toLocaleTimeString('zh-CN', { hour12: false });
  120. const color = colors[type] || colors.info;
  121. logEl.innerHTML += `<span style="color: ${color}">[${timestamp}] ${message}</span>\n`;
  122. logEl.scrollTop = logEl.scrollHeight;
  123. }
  124. function clearLog() {
  125. logEl.innerHTML = '日志已清空\n';
  126. }
  127. async function initParse() {
  128. try {
  129. log('🔄 初始化 Parse SDK...', 'info');
  130. // 从 localStorage 获取配置
  131. companyId = localStorage.getItem('company');
  132. if (!companyId) {
  133. log('❌ 未找到公司ID (localStorage.company)', 'error');
  134. document.getElementById('initStatus').innerHTML = '<span style="color:red">❌ 未找到公司ID</span>';
  135. return;
  136. }
  137. Parse.initialize(
  138. 'APPLICATION_ID',
  139. 'JAVASCRIPT_KEY'
  140. );
  141. Parse.serverURL = 'https://api.parse.yinsanse.com';
  142. Parse.masterKey = undefined;
  143. log(`✅ Parse 初始化成功`, 'success');
  144. log(`📦 公司ID: ${companyId}`, 'info');
  145. document.getElementById('initStatus').innerHTML = '<span style="color:green">✅ Parse 已初始化</span>';
  146. } catch (error) {
  147. log(`❌ 初始化失败: ${error.message}`, 'error');
  148. document.getElementById('initStatus').innerHTML = '<span style="color:red">❌ 初始化失败</span>';
  149. }
  150. }
  151. async function loadEmployees() {
  152. try {
  153. log('🔄 加载员工列表...', 'info');
  154. const query = new Parse.Query('Profile');
  155. query.equalTo('company', companyId);
  156. query.containedIn('roleName', ['组员', '组长']);
  157. query.notEqualTo('isDeleted', true);
  158. query.limit(1000);
  159. const employees = await query.find();
  160. log(`✅ 找到 ${employees.length} 个员工(组员+组长)`, 'success');
  161. const listEl = document.getElementById('employeeList');
  162. listEl.innerHTML = '';
  163. employees.forEach(emp => {
  164. const div = document.createElement('div');
  165. div.className = 'employee-card';
  166. div.innerHTML = `
  167. <div style="font-weight:600">${emp.get('name') || emp.get('realname') || '未命名'}</div>
  168. <div style="font-size:12px;color:#666;margin-top:4px">${emp.get('roleName')}</div>
  169. <div style="font-size:11px;color:#999">${emp.id.slice(0,8)}...</div>
  170. `;
  171. div.onclick = () => selectEmployee(emp.id, emp.get('name') || emp.get('realname'), div);
  172. listEl.appendChild(div);
  173. });
  174. } catch (error) {
  175. log(`❌ 加载员工列表失败: ${error.message}`, 'error');
  176. console.error(error);
  177. }
  178. }
  179. function selectEmployee(id, name, element) {
  180. selectedEmployeeId = id;
  181. document.querySelectorAll('.employee-card').forEach(el => el.classList.remove('selected'));
  182. element.classList.add('selected');
  183. document.getElementById('queryBtn').disabled = false;
  184. document.getElementById('selectedEmployee').innerHTML = `<div style="margin-top:10px;padding:10px;background:#ede9fe;border-radius:4px">
  185. <strong>已选择:</strong>${name} (ID: ${id})
  186. </div>`;
  187. log(`✅ 已选择员工: ${name} (${id})`, 'success');
  188. }
  189. async function queryEmployeeProjects() {
  190. if (!selectedEmployeeId) {
  191. log('⚠️ 请先选择一个员工', 'warning');
  192. return;
  193. }
  194. try {
  195. log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', 'info');
  196. log(`🔍 查询员工 ${selectedEmployeeId} 的项目...`, 'info');
  197. // 创建指针和字符串形式
  198. const companyPointer = {
  199. __type: 'Pointer',
  200. className: 'Company',
  201. objectId: companyId
  202. };
  203. const employeePointer = {
  204. __type: 'Pointer',
  205. className: 'Profile',
  206. objectId: selectedEmployeeId
  207. };
  208. log(`📦 查询参数:`, 'info');
  209. log(` 公司ID(字符串): "${companyId}"`, 'info');
  210. log(` 公司指针: ${JSON.stringify(companyPointer)}`, 'info');
  211. log(` 员工指针: ${JSON.stringify(employeePointer)}`, 'info');
  212. // 方法1: 查询 assignee 字段(使用公司指针)
  213. log('\n🔍 方法1a: 查询 assignee 字段 (公司指针)...', 'info');
  214. const queryByAssigneePointer = new Parse.Query('Project');
  215. queryByAssigneePointer.equalTo('company', companyPointer);
  216. queryByAssigneePointer.equalTo('assignee', employeePointer);
  217. queryByAssigneePointer.containedIn('status', ['待分配', '进行中']);
  218. queryByAssigneePointer.notEqualTo('isDeleted', true);
  219. queryByAssigneePointer.limit(100);
  220. const projectsByAssigneePointer = await queryByAssigneePointer.find();
  221. log(`${projectsByAssigneePointer.length > 0 ? '✅' : '⚠️'} 通过 assignee + 公司指针 找到 ${projectsByAssigneePointer.length} 个项目`, projectsByAssigneePointer.length > 0 ? 'success' : 'warning');
  222. // 方法1b: 查询 assignee 字段(使用公司字符串)
  223. log('\n🔍 方法1b: 查询 assignee 字段 (公司字符串)...', 'info');
  224. const queryByAssigneeString = new Parse.Query('Project');
  225. queryByAssigneeString.equalTo('company', companyId); // ✅ 使用字符串
  226. queryByAssigneeString.equalTo('assignee', employeePointer);
  227. queryByAssigneeString.containedIn('status', ['待分配', '进行中']);
  228. queryByAssigneeString.notEqualTo('isDeleted', true);
  229. queryByAssigneeString.limit(100);
  230. const projectsByAssigneeString = await queryByAssigneeString.find();
  231. log(`${projectsByAssigneeString.length > 0 ? '✅' : '⚠️'} 通过 assignee + 公司字符串 找到 ${projectsByAssigneeString.length} 个项目`, projectsByAssigneeString.length > 0 ? 'success' : 'warning');
  232. const projectsByAssignee = projectsByAssigneeString.length > 0 ? projectsByAssigneeString : projectsByAssigneePointer;
  233. if (projectsByAssignee.length > 0) {
  234. projectsByAssignee.forEach((p, i) => {
  235. log(` [${i+1}] ${p.get('title')} (${p.id})`, 'info');
  236. log(` - status: ${p.get('status')}`, 'info');
  237. log(` - currentStage: ${p.get('currentStage')}`, 'info');
  238. log(` - assignee: ${p.get('assignee')?.id}`, 'info');
  239. });
  240. }
  241. // 方法2: 查询 profile 字段(使用公司字符串)
  242. log('\n🔍 方法2: 查询 profile 字段 (公司字符串)...', 'info');
  243. const queryByProfile = new Parse.Query('Project');
  244. queryByProfile.equalTo('company', companyId); // ✅ 使用字符串
  245. queryByProfile.equalTo('profile', employeePointer);
  246. queryByProfile.containedIn('status', ['待分配', '进行中']);
  247. queryByProfile.notEqualTo('isDeleted', true);
  248. queryByProfile.limit(100);
  249. const projectsByProfile = await queryByProfile.find();
  250. log(`${projectsByProfile.length > 0 ? '✅' : '⚠️'} 通过 profile + 公司字符串 找到 ${projectsByProfile.length} 个项目`, projectsByProfile.length > 0 ? 'success' : 'warning');
  251. if (projectsByProfile.length > 0) {
  252. projectsByProfile.forEach((p, i) => {
  253. log(` [${i+1}] ${p.get('title')} (${p.id})`, 'info');
  254. log(` - status: ${p.get('status')}`, 'info');
  255. log(` - currentStage: ${p.get('currentStage')}`, 'info');
  256. log(` - profile: ${p.get('profile')?.id}`, 'info');
  257. });
  258. }
  259. // 方法3: OR 查询
  260. log('\n🔍 方法3: OR 查询 (assignee OR profile)...', 'info');
  261. const orQuery = Parse.Query.or(queryByAssignee, queryByProfile);
  262. const allProjects = await orQuery.find();
  263. log(`✅ 通过 OR 查询找到 ${allProjects.length} 个项目(去重后)`, allProjects.length > 0 ? 'success' : 'warning');
  264. // 统计
  265. log('\n📊 统计结果:', 'info');
  266. log(` - 通过 assignee + 公司指针: ${projectsByAssigneePointer.length} 个`, 'info');
  267. log(` - 通过 assignee + 公司字符串: ${projectsByAssigneeString.length} 个`, projectsByAssigneeString.length > 0 ? 'success' : 'warning');
  268. log(` - 通过 profile + 公司字符串: ${projectsByProfile.length} 个`, 'info');
  269. log(` - OR 查询总计: ${allProjects.length} 个`, 'info');
  270. log(`\n💡 建议: 使用${projectsByAssigneeString.length > 0 ? '字符串形式' : '指针形式'}的公司ID`, projectsByAssigneeString.length > 0 ? 'success' : 'warning');
  271. if (allProjects.length === 0) {
  272. log('\n⚠️ 该员工没有进行中的项目!', 'warning');
  273. log(' 可能原因:', 'warning');
  274. log(' 1. 该员工确实没有项目', 'warning');
  275. log(' 2. 项目的 status 不是"待分配"或"进行中"', 'warning');
  276. log(' 3. 项目的 assignee/profile 字段不是该员工', 'warning');
  277. log(' 4. 项目属于其他公司', 'warning');
  278. }
  279. } catch (error) {
  280. log(`❌ 查询失败: ${error.message}`, 'error');
  281. console.error(error);
  282. }
  283. }
  284. async function queryAllProjects() {
  285. try {
  286. log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', 'info');
  287. log('🔍 查询所有项目(前10个)以验证数据结构...', 'info');
  288. const query = new Parse.Query('Project');
  289. query.equalTo('company', companyId);
  290. query.containedIn('status', ['待分配', '进行中']);
  291. query.notEqualTo('isDeleted', true);
  292. query.descending('updatedAt');
  293. query.limit(10);
  294. const projects = await query.find();
  295. log(`✅ 找到 ${projects.length} 个项目`, 'success');
  296. projects.forEach((p, i) => {
  297. const assignee = p.get('assignee');
  298. const profile = p.get('profile');
  299. const company = p.get('company');
  300. log(`\n[${i+1}] ${p.get('title')}`, 'info');
  301. log(` - ID: ${p.id}`, 'info');
  302. log(` - status: ${p.get('status')}`, 'info');
  303. log(` - currentStage: ${p.get('currentStage')}`, 'info');
  304. log(` - company: ${typeof company === 'string' ? company : company?.id || '无'}`, 'info');
  305. log(` - assignee: ${assignee ? assignee.id : '无'}`, assignee ? 'success' : 'warning');
  306. log(` - profile: ${profile ? profile.id : '无'}`, profile ? 'success' : 'warning');
  307. log(` - 负责人字段: ${assignee ? 'assignee' : profile ? 'profile' : '都没有'}`, assignee || profile ? 'success' : 'error');
  308. });
  309. // 统计字段使用情况
  310. const hasAssignee = projects.filter(p => p.get('assignee')).length;
  311. const hasProfile = projects.filter(p => p.get('profile')).length;
  312. const hasNeither = projects.filter(p => !p.get('assignee') && !p.get('profile')).length;
  313. log('\n📊 负责人字段统计:', 'info');
  314. log(` - 使用 assignee: ${hasAssignee} 个`, 'info');
  315. log(` - 使用 profile: ${hasProfile} 个`, 'info');
  316. log(` - 都没有: ${hasNeither} 个`, hasNeither > 0 ? 'warning' : 'success');
  317. } catch (error) {
  318. log(`❌ 查询失败: ${error.message}`, 'error');
  319. console.error(error);
  320. }
  321. }
  322. // 页面加载时自动初始化
  323. window.onload = () => {
  324. initParse();
  325. };
  326. </script>
  327. </body>
  328. </html>