| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>调试员工项目数据</title>
- <script src="https://unpkg.com/parse@4.2.0/dist/parse.min.js"></script>
- <style>
- body {
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
- max-width: 1200px;
- margin: 20px auto;
- padding: 20px;
- background: #f5f5f5;
- }
- .card {
- background: white;
- border-radius: 8px;
- padding: 20px;
- margin-bottom: 20px;
- box-shadow: 0 2px 4px rgba(0,0,0,0.1);
- }
- h1, h2 { color: #333; }
- button {
- background: #667eea;
- color: white;
- border: none;
- padding: 12px 24px;
- border-radius: 6px;
- cursor: pointer;
- font-size: 16px;
- margin: 5px;
- }
- button:hover { background: #5568d3; }
- button:disabled { background: #ccc; cursor: not-allowed; }
- .log {
- background: #1e1e1e;
- color: #d4d4d4;
- padding: 15px;
- border-radius: 6px;
- font-family: 'Monaco', 'Menlo', 'Courier New', monospace;
- font-size: 13px;
- max-height: 600px;
- overflow-y: auto;
- white-space: pre-wrap;
- word-break: break-all;
- }
- .success { color: #4ade80; }
- .error { color: #f87171; }
- .warning { color: #fbbf24; }
- .info { color: #60a5fa; }
- select, input {
- padding: 8px 12px;
- border: 1px solid #ddd;
- border-radius: 4px;
- font-size: 14px;
- margin: 5px;
- }
- .employee-list {
- display: grid;
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
- gap: 10px;
- margin-top: 10px;
- }
- .employee-card {
- padding: 10px;
- background: #f9fafb;
- border: 1px solid #e5e7eb;
- border-radius: 4px;
- cursor: pointer;
- transition: all 0.2s;
- }
- .employee-card:hover {
- background: #ede9fe;
- border-color: #667eea;
- }
- .employee-card.selected {
- background: #ddd6fe;
- border-color: #667eea;
- font-weight: 600;
- }
- </style>
- </head>
- <body>
- <h1>🔍 员工项目数据调试工具</h1>
-
- <div class="card">
- <h2>1️⃣ Parse 初始化</h2>
- <button onclick="initParse()">初始化 Parse</button>
- <div id="initStatus"></div>
- </div>
- <div class="card">
- <h2>2️⃣ 选择员工</h2>
- <button onclick="loadEmployees()">加载员工列表</button>
- <div id="employeeList" class="employee-list"></div>
- <div id="selectedEmployee"></div>
- </div>
- <div class="card">
- <h2>3️⃣ 查询员工项目</h2>
- <button onclick="queryEmployeeProjects()" id="queryBtn" disabled>查询选中员工的项目</button>
- <button onclick="queryAllProjects()">查询所有项目(验证数据结构)</button>
- </div>
- <div class="card">
- <h2>📋 调试日志</h2>
- <button onclick="clearLog()">清空日志</button>
- <div id="log" class="log">等待操作...</div>
- </div>
- <script>
- let Parse = window.Parse;
- let logEl = document.getElementById('log');
- let selectedEmployeeId = null;
- let companyId = null;
- function log(message, type = 'info') {
- const colors = {
- success: '#4ade80',
- error: '#f87171',
- warning: '#fbbf24',
- info: '#60a5fa'
- };
- const timestamp = new Date().toLocaleTimeString('zh-CN', { hour12: false });
- const color = colors[type] || colors.info;
- logEl.innerHTML += `<span style="color: ${color}">[${timestamp}] ${message}</span>\n`;
- logEl.scrollTop = logEl.scrollHeight;
- }
- function clearLog() {
- logEl.innerHTML = '日志已清空\n';
- }
- async function initParse() {
- try {
- log('🔄 初始化 Parse SDK...', 'info');
-
- // 从 localStorage 获取配置
- companyId = localStorage.getItem('company');
-
- if (!companyId) {
- log('❌ 未找到公司ID (localStorage.company)', 'error');
- document.getElementById('initStatus').innerHTML = '<span style="color:red">❌ 未找到公司ID</span>';
- return;
- }
- Parse.initialize(
- 'APPLICATION_ID',
- 'JAVASCRIPT_KEY'
- );
- Parse.serverURL = 'https://api.parse.yinsanse.com';
- Parse.masterKey = undefined;
- log(`✅ Parse 初始化成功`, 'success');
- log(`📦 公司ID: ${companyId}`, 'info');
- document.getElementById('initStatus').innerHTML = '<span style="color:green">✅ Parse 已初始化</span>';
- } catch (error) {
- log(`❌ 初始化失败: ${error.message}`, 'error');
- document.getElementById('initStatus').innerHTML = '<span style="color:red">❌ 初始化失败</span>';
- }
- }
- async function loadEmployees() {
- try {
- log('🔄 加载员工列表...', 'info');
-
- const query = new Parse.Query('Profile');
- query.equalTo('company', companyId);
- query.containedIn('roleName', ['组员', '组长']);
- query.notEqualTo('isDeleted', true);
- query.limit(1000);
-
- const employees = await query.find();
- log(`✅ 找到 ${employees.length} 个员工(组员+组长)`, 'success');
-
- const listEl = document.getElementById('employeeList');
- listEl.innerHTML = '';
-
- employees.forEach(emp => {
- const div = document.createElement('div');
- div.className = 'employee-card';
- div.innerHTML = `
- <div style="font-weight:600">${emp.get('name') || emp.get('realname') || '未命名'}</div>
- <div style="font-size:12px;color:#666;margin-top:4px">${emp.get('roleName')}</div>
- <div style="font-size:11px;color:#999">${emp.id.slice(0,8)}...</div>
- `;
- div.onclick = () => selectEmployee(emp.id, emp.get('name') || emp.get('realname'), div);
- listEl.appendChild(div);
- });
-
- } catch (error) {
- log(`❌ 加载员工列表失败: ${error.message}`, 'error');
- console.error(error);
- }
- }
- function selectEmployee(id, name, element) {
- selectedEmployeeId = id;
- document.querySelectorAll('.employee-card').forEach(el => el.classList.remove('selected'));
- element.classList.add('selected');
- document.getElementById('queryBtn').disabled = false;
- document.getElementById('selectedEmployee').innerHTML = `<div style="margin-top:10px;padding:10px;background:#ede9fe;border-radius:4px">
- <strong>已选择:</strong>${name} (ID: ${id})
- </div>`;
- log(`✅ 已选择员工: ${name} (${id})`, 'success');
- }
- async function queryEmployeeProjects() {
- if (!selectedEmployeeId) {
- log('⚠️ 请先选择一个员工', 'warning');
- return;
- }
- try {
- log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', 'info');
- log(`🔍 查询员工 ${selectedEmployeeId} 的项目...`, 'info');
-
- // 创建指针和字符串形式
- const companyPointer = {
- __type: 'Pointer',
- className: 'Company',
- objectId: companyId
- };
-
- const employeePointer = {
- __type: 'Pointer',
- className: 'Profile',
- objectId: selectedEmployeeId
- };
-
- log(`📦 查询参数:`, 'info');
- log(` 公司ID(字符串): "${companyId}"`, 'info');
- log(` 公司指针: ${JSON.stringify(companyPointer)}`, 'info');
- log(` 员工指针: ${JSON.stringify(employeePointer)}`, 'info');
-
- // 方法1: 查询 assignee 字段(使用公司指针)
- log('\n🔍 方法1a: 查询 assignee 字段 (公司指针)...', 'info');
- const queryByAssigneePointer = new Parse.Query('Project');
- queryByAssigneePointer.equalTo('company', companyPointer);
- queryByAssigneePointer.equalTo('assignee', employeePointer);
- queryByAssigneePointer.containedIn('status', ['待分配', '进行中']);
- queryByAssigneePointer.notEqualTo('isDeleted', true);
- queryByAssigneePointer.limit(100);
-
- const projectsByAssigneePointer = await queryByAssigneePointer.find();
- log(`${projectsByAssigneePointer.length > 0 ? '✅' : '⚠️'} 通过 assignee + 公司指针 找到 ${projectsByAssigneePointer.length} 个项目`, projectsByAssigneePointer.length > 0 ? 'success' : 'warning');
-
- // 方法1b: 查询 assignee 字段(使用公司字符串)
- log('\n🔍 方法1b: 查询 assignee 字段 (公司字符串)...', 'info');
- const queryByAssigneeString = new Parse.Query('Project');
- queryByAssigneeString.equalTo('company', companyId); // ✅ 使用字符串
- queryByAssigneeString.equalTo('assignee', employeePointer);
- queryByAssigneeString.containedIn('status', ['待分配', '进行中']);
- queryByAssigneeString.notEqualTo('isDeleted', true);
- queryByAssigneeString.limit(100);
-
- const projectsByAssigneeString = await queryByAssigneeString.find();
- log(`${projectsByAssigneeString.length > 0 ? '✅' : '⚠️'} 通过 assignee + 公司字符串 找到 ${projectsByAssigneeString.length} 个项目`, projectsByAssigneeString.length > 0 ? 'success' : 'warning');
-
- const projectsByAssignee = projectsByAssigneeString.length > 0 ? projectsByAssigneeString : projectsByAssigneePointer;
-
- if (projectsByAssignee.length > 0) {
- projectsByAssignee.forEach((p, i) => {
- log(` [${i+1}] ${p.get('title')} (${p.id})`, 'info');
- log(` - status: ${p.get('status')}`, 'info');
- log(` - currentStage: ${p.get('currentStage')}`, 'info');
- log(` - assignee: ${p.get('assignee')?.id}`, 'info');
- });
- }
-
- // 方法2: 查询 profile 字段(使用公司字符串)
- log('\n🔍 方法2: 查询 profile 字段 (公司字符串)...', 'info');
- const queryByProfile = new Parse.Query('Project');
- queryByProfile.equalTo('company', companyId); // ✅ 使用字符串
- queryByProfile.equalTo('profile', employeePointer);
- queryByProfile.containedIn('status', ['待分配', '进行中']);
- queryByProfile.notEqualTo('isDeleted', true);
- queryByProfile.limit(100);
-
- const projectsByProfile = await queryByProfile.find();
- log(`${projectsByProfile.length > 0 ? '✅' : '⚠️'} 通过 profile + 公司字符串 找到 ${projectsByProfile.length} 个项目`, projectsByProfile.length > 0 ? 'success' : 'warning');
-
- if (projectsByProfile.length > 0) {
- projectsByProfile.forEach((p, i) => {
- log(` [${i+1}] ${p.get('title')} (${p.id})`, 'info');
- log(` - status: ${p.get('status')}`, 'info');
- log(` - currentStage: ${p.get('currentStage')}`, 'info');
- log(` - profile: ${p.get('profile')?.id}`, 'info');
- });
- }
-
- // 方法3: OR 查询
- log('\n🔍 方法3: OR 查询 (assignee OR profile)...', 'info');
- const orQuery = Parse.Query.or(queryByAssignee, queryByProfile);
- const allProjects = await orQuery.find();
- log(`✅ 通过 OR 查询找到 ${allProjects.length} 个项目(去重后)`, allProjects.length > 0 ? 'success' : 'warning');
-
- // 统计
- log('\n📊 统计结果:', 'info');
- log(` - 通过 assignee + 公司指针: ${projectsByAssigneePointer.length} 个`, 'info');
- log(` - 通过 assignee + 公司字符串: ${projectsByAssigneeString.length} 个`, projectsByAssigneeString.length > 0 ? 'success' : 'warning');
- log(` - 通过 profile + 公司字符串: ${projectsByProfile.length} 个`, 'info');
- log(` - OR 查询总计: ${allProjects.length} 个`, 'info');
- log(`\n💡 建议: 使用${projectsByAssigneeString.length > 0 ? '字符串形式' : '指针形式'}的公司ID`, projectsByAssigneeString.length > 0 ? 'success' : 'warning');
-
- if (allProjects.length === 0) {
- log('\n⚠️ 该员工没有进行中的项目!', 'warning');
- log(' 可能原因:', 'warning');
- log(' 1. 该员工确实没有项目', 'warning');
- log(' 2. 项目的 status 不是"待分配"或"进行中"', 'warning');
- log(' 3. 项目的 assignee/profile 字段不是该员工', 'warning');
- log(' 4. 项目属于其他公司', 'warning');
- }
-
- } catch (error) {
- log(`❌ 查询失败: ${error.message}`, 'error');
- console.error(error);
- }
- }
- async function queryAllProjects() {
- try {
- log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━', 'info');
- log('🔍 查询所有项目(前10个)以验证数据结构...', 'info');
-
- const query = new Parse.Query('Project');
- query.equalTo('company', companyId);
- query.containedIn('status', ['待分配', '进行中']);
- query.notEqualTo('isDeleted', true);
- query.descending('updatedAt');
- query.limit(10);
-
- const projects = await query.find();
- log(`✅ 找到 ${projects.length} 个项目`, 'success');
-
- projects.forEach((p, i) => {
- const assignee = p.get('assignee');
- const profile = p.get('profile');
- const company = p.get('company');
-
- log(`\n[${i+1}] ${p.get('title')}`, 'info');
- log(` - ID: ${p.id}`, 'info');
- log(` - status: ${p.get('status')}`, 'info');
- log(` - currentStage: ${p.get('currentStage')}`, 'info');
- log(` - company: ${typeof company === 'string' ? company : company?.id || '无'}`, 'info');
- log(` - assignee: ${assignee ? assignee.id : '无'}`, assignee ? 'success' : 'warning');
- log(` - profile: ${profile ? profile.id : '无'}`, profile ? 'success' : 'warning');
- log(` - 负责人字段: ${assignee ? 'assignee' : profile ? 'profile' : '都没有'}`, assignee || profile ? 'success' : 'error');
- });
-
- // 统计字段使用情况
- const hasAssignee = projects.filter(p => p.get('assignee')).length;
- const hasProfile = projects.filter(p => p.get('profile')).length;
- const hasNeither = projects.filter(p => !p.get('assignee') && !p.get('profile')).length;
-
- log('\n📊 负责人字段统计:', 'info');
- log(` - 使用 assignee: ${hasAssignee} 个`, 'info');
- log(` - 使用 profile: ${hasProfile} 个`, 'info');
- log(` - 都没有: ${hasNeither} 个`, hasNeither > 0 ? 'warning' : 'success');
-
- } catch (error) {
- log(`❌ 查询失败: ${error.message}`, 'error');
- console.error(error);
- }
- }
- // 页面加载时自动初始化
- window.onload = () => {
- initParse();
- };
- </script>
- </body>
- </html>
|