初始提交:人事共享服务中心钉钉登录功能
This commit is contained in:
48
templates/admin_demands.html
Normal file
48
templates/admin_demands.html
Normal file
@@ -0,0 +1,48 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}管理后台{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>管理后台 - 所有需求</h2>
|
||||
{% if demands %}
|
||||
<div class="question-list">
|
||||
{% for demand in demands %}
|
||||
<div class="question-item">
|
||||
<div class="question-header">
|
||||
<span class="branch">{{ get_branch_name(demand.branch) }}</span>
|
||||
<span class="time">{{ demand.created_at.strftime('%Y-%m-%d %H:%M') }}</span>
|
||||
<span class="status {% if demand.is_public %}public{% else %}private{% endif %}">
|
||||
{% if demand.is_public %}公开{% else %}私有{% endif %}
|
||||
</span>
|
||||
<span class="user">提交者: {{ demand.user.username }}</span>
|
||||
</div>
|
||||
<h3 class="demand-title">{{ demand.title }}</h3>
|
||||
<p class="content">{{ demand.content }}</p>
|
||||
<div class="question-footer">
|
||||
<span class="contact">联系方式: {{ demand.contact }}</span>
|
||||
{% if demand.answer %}
|
||||
<div class="answer">
|
||||
<strong>回答:</strong>
|
||||
<p>{{ demand.answer }}</p>
|
||||
<span class="answer-time">回答时间: {{ demand.answered_at.strftime('%Y-%m-%d %H:%M') }}</span>
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="status waiting">待回答</span>
|
||||
{% endif %}
|
||||
<div class="actions">
|
||||
<a href="{{ url_for('edit_demand', id=demand.id) }}" class="btn btn-edit">编辑</a>
|
||||
<a href="{{ url_for('answer_demand', id=demand.id) }}" class="btn btn-answer">回答</a>
|
||||
<form method="POST" action="{{ url_for('toggle_public', id=demand.id) }}" style="display: inline;">
|
||||
<button type="submit" class="btn btn-toggle">
|
||||
{% if demand.is_public %}设为私有{% else %}设为公开{% endif %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="empty">暂无需求</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
24
templates/answer_form.html
Normal file
24
templates/answer_form.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}回答需求{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>回答需求</h2>
|
||||
<div class="question-preview">
|
||||
<h3>需求内容</h3>
|
||||
<h4 class="demand-title">{{ demand.title }}</h4>
|
||||
<p>{{ demand.content }}</p>
|
||||
<p class="meta">分会: {{ get_branch_name(demand.branch) }} | 联系方式: {{ demand.contact }}</p>
|
||||
</div>
|
||||
<form method="POST" class="form">
|
||||
{{ form.hidden_tag() }}
|
||||
<div class="form-group">
|
||||
{{ form.answer.label }}
|
||||
{{ form.answer(class="form-control", rows=8) }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
{{ form.submit(class="btn btn-primary") }}
|
||||
<a href="{{ url_for('index') }}" class="btn btn-cancel">取消</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
47
templates/base.html
Normal file
47
templates/base.html
Normal file
@@ -0,0 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{% block title %}人事共享服务中心"码"上办{% endblock %}</title>
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="container">
|
||||
<h1>
|
||||
<img src="{{ url_for('static', filename='logo.png') }}" alt="logo" class="header-logo">
|
||||
<a href="{{ url_for('index') }}">人事共享服务中心"码"上办</a>
|
||||
</h1>
|
||||
<nav>
|
||||
<ul>
|
||||
<li><a href="{{ url_for('index') }}">需求汇总</a></li>
|
||||
{% if current_user.is_authenticated %}
|
||||
<li><a href="{{ url_for('new_demand') }}">提交新需求</a></li>
|
||||
<li><a href="{{ url_for('my_demands') }}">我的需求</a></li>
|
||||
{% if current_user.is_admin() %}
|
||||
<li><a href="{{ url_for('admin_demands') }}">管理后台</a></li>
|
||||
{% endif %}
|
||||
<li class="user-name">{{ current_user.dingtalk_name or current_user.username }}</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
</div>
|
||||
</header>
|
||||
<main class="container">
|
||||
{% with messages = get_flashed_messages() %}
|
||||
{% if messages %}
|
||||
{% for message in messages %}
|
||||
<div class="flash">{{ message }}</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
<footer>
|
||||
<div class="container">
|
||||
<p>© 2026 人事共享服务中心"码"上办</p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
31
templates/demand_form.html
Normal file
31
templates/demand_form.html
Normal file
@@ -0,0 +1,31 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>{{ title }}</h2>
|
||||
<form method="POST" class="form">
|
||||
{{ form.hidden_tag() }}
|
||||
<div class="form-group">
|
||||
<label>{{ form.title.label.text }} *</label>
|
||||
{{ form.title(class="form-control") }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>{{ form.content.label.text }} *</label>
|
||||
{{ form.content(class="form-control textarea") }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>{{ form.branch.label.text }}</label>
|
||||
{{ form.branch(class="form-control") }}
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>{{ form.contact.label.text }} *</label>
|
||||
{{ form.contact(class="form-control") }}
|
||||
</div>
|
||||
{{ form.is_public() }} <label for="is_public">{{ form.is_public.label.text }}</label>
|
||||
<div class="form-group">
|
||||
{{ form.submit(class="btn btn-primary") }}
|
||||
<a href="{{ url_for('index') }}" class="btn btn-cancel">取消</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
186
templates/dingtalk_entry.html
Normal file
186
templates/dingtalk_entry.html
Normal file
@@ -0,0 +1,186 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||||
<title>人事共享服务中心"码"上办</title>
|
||||
<script src="https://g.alicdn.com/dingding/dingtalk-jsapi/3.0.15/dingtalk.open.js"></script>
|
||||
<!-- Aplus SDK for performance monitoring -->
|
||||
<script src="https://g.alicdn.com/a-plus/a-plus-web-sdk/1.1.2/index.js"></script>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
.container {
|
||||
text-align: center;
|
||||
color: white;
|
||||
}
|
||||
.loading {
|
||||
font-size: 18px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.error {
|
||||
font-size: 16px;
|
||||
color: #ffcccc;
|
||||
margin-top: 20px;
|
||||
padding: 15px;
|
||||
background: rgba(255,0,0,0.2);
|
||||
border-radius: 8px;
|
||||
}
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(255,255,255,0.3);
|
||||
border-top-color: white;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 0 auto 20px;
|
||||
}
|
||||
@keyframes spin {
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.logo {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<img src="/static/logo.png" alt="logo" class="logo">
|
||||
<div class="spinner"></div>
|
||||
<p class="loading">正在跳转...</p>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
console.log('[前端] ============== 钉钉登录流程开始 ==============');
|
||||
const APP_KEY = '{{ app_key }}';
|
||||
const TARGET_URL = '{{ target_url }}';
|
||||
const CORP_ID = '{{ corp_id }}';
|
||||
const AGENT_ID = '{{ agent_id }}';
|
||||
console.log('[前端] 配置信息:', { APP_KEY, TARGET_URL, CORP_ID, AGENT_ID });
|
||||
|
||||
function showError(msg) {
|
||||
console.error('[前端] 错误:', msg);
|
||||
document.querySelector('.loading').textContent = '配置失败';
|
||||
document.querySelector('.spinner').style.display = 'none';
|
||||
const errorDiv = document.createElement('div');
|
||||
errorDiv.className = 'error';
|
||||
errorDiv.textContent = msg;
|
||||
document.querySelector('.container').appendChild(errorDiv);
|
||||
}
|
||||
|
||||
function getCleanUrl() {
|
||||
var url = window.location.protocol + '//' + window.location.hostname;
|
||||
if (window.location.port && window.location.port !== '80' && window.location.port !== '443') {
|
||||
url += ':' + window.location.port;
|
||||
}
|
||||
url += window.location.pathname;
|
||||
return url;
|
||||
}
|
||||
|
||||
function handleAuthCode(code) {
|
||||
console.log('[前端] 3. 获取到 code:', code);
|
||||
console.log('[前端] 4. 开始请求后端 /requirement-collection/dingtalk/api/getDingUser?code=' + code);
|
||||
|
||||
fetch('/requirement-collection/dingtalk/api/getDingUser?code=' + code)
|
||||
.then(function(resp) {
|
||||
console.log('[前端] 5. 收到后端响应, 状态码:', resp.status);
|
||||
return resp.json();
|
||||
})
|
||||
.then(function(user) {
|
||||
console.log('[前端] 6. 解析响应数据:', user);
|
||||
|
||||
if (user.userid) {
|
||||
var userId = user.userid;
|
||||
var name = user.name || user.nickname || '';
|
||||
var dept = user.department || '';
|
||||
if (Array.isArray(dept)) {
|
||||
dept = dept.join(',');
|
||||
}
|
||||
|
||||
console.log('[前端] 7. 用户信息解析:', { userId: userId, name: name, dept: dept });
|
||||
var targetUrl = TARGET_URL + '?userId=' + encodeURIComponent(userId) + '&name=' + encodeURIComponent(name) + '&dept=' + encodeURIComponent(dept);
|
||||
console.log('[前端] 8. 准备跳转:', targetUrl);
|
||||
window.location.href = targetUrl;
|
||||
} else {
|
||||
console.error('[前端] 7. 无userid, 无法登录');
|
||||
showError('获取用户信息失败,请重试');
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error('[前端] 6. fetch请求失败:', err);
|
||||
showError('网络请求失败,请重试');
|
||||
});
|
||||
}
|
||||
|
||||
function initDingTalk() {
|
||||
var url = getCleanUrl();
|
||||
console.log('[前端] 0. 当前页面URL (清理后):', url);
|
||||
|
||||
var apiUrl = '/requirement-collection/dingtalk/api/getSignature?url=' + encodeURIComponent(url);
|
||||
console.log('[前端] 1. 请求后端获取签名:', apiUrl);
|
||||
|
||||
fetch(apiUrl)
|
||||
.then(function(resp) {
|
||||
console.log('[前端] 2. 收到响应, 状态码:', resp.status);
|
||||
return resp.json();
|
||||
})
|
||||
.then(function(data) {
|
||||
console.log('[前端] 2. 收到签名数据:', data);
|
||||
|
||||
if (!data.signature) {
|
||||
showError('获取签名失败: ' + (data.error || '未知错误'));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('[前端] 3. 配置dd.config...');
|
||||
dd.config({
|
||||
agentId: data.agentId ? Number(data.agentId) : (AGENT_ID ? Number(AGENT_ID) : 0),
|
||||
corpId: CORP_ID,
|
||||
timeStamp: data.timeStamp,
|
||||
nonceStr: data.nonceStr,
|
||||
signature: data.signature,
|
||||
type: 0,
|
||||
jsApiList: ['runtime.permission.requestAuthCode']
|
||||
});
|
||||
|
||||
dd.ready(function() {
|
||||
console.log('[前端] 4. dd.ready 成功 - 钉钉SDK已就绪');
|
||||
console.log('[前端] 5. 开始调用 requestAuthCode 获取授权码...');
|
||||
|
||||
dd.runtime.permission.requestAuthCode({
|
||||
corpId: CORP_ID,
|
||||
onSuccess: function(res) {
|
||||
handleAuthCode(res.code);
|
||||
},
|
||||
onFail: function(err) {
|
||||
console.error('[前端] 6. requestAuthCode 失败:', err);
|
||||
showError('获取授权码失败: ' + JSON.stringify(err));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
dd.error(function(err) {
|
||||
console.error('[前端] 4. dd.error - 钉钉SDK配置失败:', err);
|
||||
showError('钉钉SDK配置失败: ' + JSON.stringify(err));
|
||||
});
|
||||
})
|
||||
.catch(function(err) {
|
||||
console.error('[前端] 1. 请求签名失败:', err);
|
||||
showError('初始化失败: ' + err.message);
|
||||
});
|
||||
}
|
||||
|
||||
initDingTalk();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
51
templates/index.html
Normal file
51
templates/index.html
Normal file
@@ -0,0 +1,51 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}需求汇总{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>需求汇总</h2>
|
||||
{% if demands %}
|
||||
<div class="question-list">
|
||||
{% for demand in demands %}
|
||||
<div class="question-item">
|
||||
<h3 class="demand-title">{{ demand.title }} <span class="branch">{{ get_branch_name(demand.branch) }}</span></h3>
|
||||
<span class="time">{{ demand.created_at.strftime('%Y-%m-%d %H:%M') }}</span>
|
||||
<p class="content">{{ demand.content }}</p>
|
||||
{% if demand.answer %}
|
||||
<div class="answer-toggle" onclick="toggleAnswer(this)">
|
||||
<span class="toggle-icon">▶</span>
|
||||
<span class="toggle-text">查看回答</span>
|
||||
</div>
|
||||
<div class="answer-content" style="display: none;">
|
||||
<div class="answer">
|
||||
<strong>回答:</strong>
|
||||
<p>{{ demand.answer }}</p>
|
||||
<span class="answer-time">回答时间: {{ demand.answered_at.strftime('%Y-%m-%d %H:%M') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="empty">暂无公开需求</p>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
function toggleAnswer(element) {
|
||||
var content = element.nextElementSibling;
|
||||
var icon = element.querySelector('.toggle-icon');
|
||||
var text = element.querySelector('.toggle-text');
|
||||
|
||||
if (content.style.display === 'none') {
|
||||
content.style.display = 'block';
|
||||
icon.textContent = '▼';
|
||||
text.textContent = '收起回答';
|
||||
} else {
|
||||
content.style.display = 'none';
|
||||
icon.textContent = '▶';
|
||||
text.textContent = '查看回答';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
56
templates/my_demands.html
Normal file
56
templates/my_demands.html
Normal file
@@ -0,0 +1,56 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}我的需求{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>我的需求</h2>
|
||||
{% if demands %}
|
||||
<div class="question-list">
|
||||
{% for demand in demands %}
|
||||
<div class="question-item">
|
||||
<h3 class="demand-title">{{ demand.title }} <span class="branch">{{ get_branch_name(demand.branch) }}</span></h3>
|
||||
<span class="time">{{ demand.created_at.strftime('%Y-%m-%d %H:%M') }}</span>
|
||||
<p class="content">{{ demand.content }}</p>
|
||||
{% if demand.answer %}
|
||||
<div class="answer-toggle" onclick="toggleAnswer(this)">
|
||||
<span class="toggle-icon">▶</span>
|
||||
<span class="toggle-text">查看回答</span>
|
||||
</div>
|
||||
<div class="answer-content" style="display: none;">
|
||||
<div class="answer">
|
||||
<strong>回答:</strong>
|
||||
<p>{{ demand.answer }}</p>
|
||||
<span class="answer-time">回答时间: {{ demand.answered_at.strftime('%Y-%m-%d %H:%M') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if not demand.answer %}
|
||||
<div class="actions">
|
||||
<a href="{{ url_for('edit_demand', id=demand.id) }}" class="btn btn-edit">编辑</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<p class="empty">暂无需求</p>
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
function toggleAnswer(element) {
|
||||
var content = element.nextElementSibling;
|
||||
var icon = element.querySelector('.toggle-icon');
|
||||
var text = element.querySelector('.toggle-text');
|
||||
|
||||
if (content.style.display === 'none') {
|
||||
content.style.display = 'block';
|
||||
icon.textContent = '▼';
|
||||
text.textContent = '收起回答';
|
||||
} else {
|
||||
content.style.display = 'none';
|
||||
icon.textContent = '▶';
|
||||
text.textContent = '查看回答';
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
11
templates/not_logged_in.html
Normal file
11
templates/not_logged_in.html
Normal file
@@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
|
||||
{% block title %}未登录 - 人事共享服务中心"码"上办{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="login-required">
|
||||
<div class="login-icon">🔒</div>
|
||||
<h2>请先通过钉钉登录</h2>
|
||||
<p>本系统需要通过钉钉账号登录才能使用</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user