'init'
This commit is contained in:
Binary file not shown.
10
sc-lktx-backend/scripts/_check_depts.py
Normal file
10
sc-lktx-backend/scripts/_check_depts.py
Normal file
@@ -0,0 +1,10 @@
|
||||
import requests, os
|
||||
|
||||
token = os.environ.get("ADMIN_TOKEN", "")
|
||||
if not token:
|
||||
token = input("ADMIN_TOKEN: ").strip()
|
||||
|
||||
resp = requests.get("http://localhost:28175/v1/admin/departments-all",
|
||||
headers={"Authorization": f"Bearer {token}"})
|
||||
for d in resp.json().get("data", []):
|
||||
print(f'{d["id"]:>3d} | {d["name"]}')
|
||||
20
sc-lktx-backend/scripts/fix_appointments.py
Normal file
20
sc-lktx-backend/scripts/fix_appointments.py
Normal file
@@ -0,0 +1,20 @@
|
||||
"""Remove audit refs and functions from Appointments.vue"""
|
||||
with open('mp-sc-admin/src/pages/Appointments.vue', 'rb') as f:
|
||||
content = f.read()
|
||||
|
||||
# Lines to remove - the refs
|
||||
old1 = b"const auditVisible = ref(false)\r\nconst auditTarget = ref<any>(null)\r\nconst auditStatus = ref(1)\r\nconst auditComment = ref('')\r\nconst auditing = ref(false)\r\n"
|
||||
new1 = b""
|
||||
content = content.replace(old1, new1)
|
||||
|
||||
# The functions handleAudit + submitAudit
|
||||
old2 = b"function handleAudit(row, status) {\r\n auditTarget.value = row; auditStatus.value = status; auditComment.value = ''; auditVisible.value = true\r\n}\r\nasync function submitAudit() {\r\n auditing.value = true\r\n try {\r\n const status = auditStatus.value\r\n"
|
||||
content = content.replace(old2, b"")
|
||||
|
||||
# Find the closing part of submitAudit and remove it
|
||||
old3 = b" await request.Put(`/v1/admin/appointments/${auditTarget.value.id}/audit`, { status, comment: auditComment.value })\r\n ElMessage.success(`${text}\xe6\x88\x90\xe5\x8a\x9f`)\r\n auditVisible.value = false\r\n await fetchData()\r\n } catch (err) { ElMessage.error(err.message || '\xe6\x93\x8d\xe4\xbd\x9c\xe5\xa4\xb1\xe8\xb4\xa5') }\r\n finally { auditing.value = false }\r\n}\r\n"
|
||||
content = content.replace(old3, b"")
|
||||
|
||||
with open('mp-sc-admin/src/pages/Appointments.vue', 'wb') as f:
|
||||
f.write(content)
|
||||
print('done')
|
||||
44
sc-lktx-backend/scripts/fix_menu.py
Normal file
44
sc-lktx-backend/scripts/fix_menu.py
Normal file
@@ -0,0 +1,44 @@
|
||||
"""Move workflow-designer menu item from 业务管理 to 访客模块"""
|
||||
with open('mp-sc-admin/src/pages/Layout.vue', 'rb') as f:
|
||||
content = f.read()
|
||||
|
||||
# File uses \r\n line endings, so search with \r\n
|
||||
old = (
|
||||
b" { path: '/pending-employees', label: '\xe9\x80\x9a\xe8\xae\xaf\xe5\xbd\x95\xe5\xaf\xbc\xe5\x85\xa5', icon: 'i-carbon-import-export' },\n"
|
||||
b" { path: '/workflow-designer', label: '\xe6\xb5\x81\xe7\xa8\x8b\xe8\xae\xbe\xe8\xae\xa1\xe5\x99\xa8', icon: 'i-carbon-diagram' },\r\n"
|
||||
b" ],\r\n"
|
||||
b" },\r\n"
|
||||
b" {\r\n"
|
||||
b" label: '\xe8\xae\xbf\xe5\xae\xa2\xe6\xa8\xa1\xe5\x9d\x97',\r\n"
|
||||
b" items: [\r\n"
|
||||
b" { path: '/appointments', label: '\xe9\xa2\x84\xe7\xba\xa6\xe7\xae\xa1\xe7\x90\x86', icon: 'i-carbon-calendar' },\r\n"
|
||||
b" { path: '/visit-types', label: '\xe6\x9d\xa5\xe8\xae\xbf\xe7\x9b\xae\xe7\x9a\x84', icon: 'i-carbon-list-dropdown' },\r\n"
|
||||
b" { path: '/visitor-areas', label: '\xe5\x88\xb0\xe8\xae\xbf\xe5\x8c\xba\xe5\x9f\x9f', icon: 'i-carbon-location' },\r\n"
|
||||
b" { path: '/config', label: '\xe5\xae\xa1\xe6\x89\xb9\xe9\x85\x8d\xe7\xbd\xae', icon: 'i-carbon-settings-adjust' },"
|
||||
)
|
||||
|
||||
new = (
|
||||
b" { path: '/pending-employees', label: '\xe9\x80\x9a\xe8\xae\xaf\xe5\xbd\x95\xe5\xaf\xbc\xe5\x85\xa5', icon: 'i-carbon-import-export' },\n"
|
||||
b" ],\r\n"
|
||||
b" },\r\n"
|
||||
b" {\r\n"
|
||||
b" label: '\xe8\xae\xbf\xe5\xae\xa2\xe6\xa8\xa1\xe5\x9d\x97',\r\n"
|
||||
b" items: [\r\n"
|
||||
b" { path: '/appointments', label: '\xe9\xa2\x84\xe7\xba\xa6\xe7\xae\xa1\xe7\x90\x86', icon: 'i-carbon-calendar' },\r\n"
|
||||
b" { path: '/visit-types', label: '\xe6\x9d\xa5\xe8\xae\xbf\xe7\x9b\xae\xe7\x9a\x84', icon: 'i-carbon-list-dropdown' },\r\n"
|
||||
b" { path: '/visitor-areas', label: '\xe5\x88\xb0\xe8\xae\xbf\xe5\x8c\xba\xe5\x9f\x9f', icon: 'i-carbon-location' },\r\n"
|
||||
b" { path: '/workflow-designer', label: '\xe6\xb5\x81\xe7\xa8\x8b\xe8\xae\xbe\xe8\xae\xa1\xe5\x99\xa8', icon: 'i-carbon-diagram' },\r\n"
|
||||
b" { path: '/config', label: '\xe5\xae\xa1\xe6\x89\xb9\xe9\x85\x8d\xe7\xbd\xae', icon: 'i-carbon-settings-adjust' },"
|
||||
)
|
||||
|
||||
if old in content:
|
||||
content = content.replace(old, new)
|
||||
with open('mp-sc-admin/src/pages/Layout.vue', 'wb') as f:
|
||||
f.write(content)
|
||||
print('done - replaced successfully')
|
||||
else:
|
||||
print('pattern not found')
|
||||
idx = content.find(b'pending-employees')
|
||||
if idx >= 0:
|
||||
# Show bytes around it
|
||||
print(content[idx:idx+400])
|
||||
100
sc-lktx-backend/scripts/gen-swagger.ps1
Normal file
100
sc-lktx-backend/scripts/gen-swagger.ps1
Normal file
@@ -0,0 +1,100 @@
|
||||
# 生成 Swagger 文档
|
||||
# 用法: .\scripts\gen-swagger.ps1
|
||||
|
||||
$swag = "$env:USERPROFILE\go\bin\swag.exe"
|
||||
$project = "E:\workspaces\sc-lktx-mp\sc-lktx-backend"
|
||||
$output = "$project\docs"
|
||||
|
||||
Write-Host "=== 1. 生成 Swagger 文档 ==="
|
||||
& $swag init -g cmd/server/main.go --output $output --parseDependency --parseDepth 1
|
||||
|
||||
if ($LASTEXITCODE -ne 0 -and $LASTEXITCODE -ne 1) {
|
||||
Write-Host "swag init failed with code $LASTEXITCODE" -ForegroundColor Red
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
|
||||
Write-Host "=== 2. 转换鉴权格式 (apiKey -> Bearer, 全局继承) ==="
|
||||
$json = Get-Content "$output\swagger.json" -Raw | ConvertFrom-Json
|
||||
|
||||
$bearerScheme = @{
|
||||
type = "http"
|
||||
scheme = "bearer"
|
||||
bearerFormat = "JWT"
|
||||
}
|
||||
|
||||
$json.PSObject.Properties.Remove('securityDefinitions')
|
||||
|
||||
$components = $json.components
|
||||
if (-not $components) {
|
||||
$components = New-Object PSObject
|
||||
$json | Add-Member -Name 'components' -Value $components -MemberType NoteProperty -Force
|
||||
}
|
||||
$components | Add-Member -Name 'securitySchemes' -Value @{ BearerAuth = $bearerScheme } -MemberType NoteProperty -Force
|
||||
|
||||
# 移除所有接口上的 security(避免覆盖全局继承)
|
||||
function Remove-SecurityFromPaths($obj) {
|
||||
if ($obj.PSObject.Properties.Name -contains 'security') {
|
||||
$obj.PSObject.Properties.Remove('security')
|
||||
}
|
||||
foreach ($prop in $obj.PSObject.Properties) {
|
||||
if ($prop.Value -is [PSCustomObject]) {
|
||||
Remove-SecurityFromPaths $prop.Value
|
||||
} elseif ($prop.Value -is [System.Collections.ICollection]) {
|
||||
foreach ($item in $prop.Value) {
|
||||
if ($item -is [PSCustomObject]) {
|
||||
Remove-SecurityFromPaths $item
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Remove-SecurityFromPaths $json.paths
|
||||
|
||||
# 添加全局 security(所有接口继承此鉴权)
|
||||
$json | Add-Member -Name 'security' -Value @(@{ BearerAuth = @() }) -MemberType NoteProperty -Force
|
||||
|
||||
$json | ConvertTo-Json -Depth 32 | Set-Content "$output\swagger.json"
|
||||
|
||||
# 同步修改 docs.go
|
||||
$docsPath = "$output\docs.go"
|
||||
$content = Get-Content $docsPath -Raw
|
||||
|
||||
# 替换 securityDefinitions 为 components/securitySchemes (bearer 格式)
|
||||
$oldSecurity = '"securityDefinitions": {
|
||||
"BearerAuth": {
|
||||
"type": "apiKey",
|
||||
"name": "Authorization",
|
||||
"in": "header"
|
||||
}
|
||||
},'
|
||||
$newSecurity = '"components": {
|
||||
"securitySchemes": {
|
||||
"BearerAuth": {
|
||||
"bearerFormat": "JWT",
|
||||
"type": "http",
|
||||
"scheme": "bearer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],'
|
||||
$content = $content.Replace($oldSecurity, $newSecurity)
|
||||
|
||||
# 移除所有接口上的 security(避免覆盖全局继承)
|
||||
$content = $content -replace '\s{12,}"security": \[\s*\{\s*"BearerAuth":\s*\[\]\s*\}\s*\],?\s*', "`r`n"
|
||||
Write-Host " 已移除接口级 security"
|
||||
|
||||
Set-Content $docsPath $content
|
||||
Write-Host " docs.go 同步更新(全局继承)"
|
||||
|
||||
# 清理临时文件,只保留 docs.go
|
||||
Remove-Item "$output\swagger.json", "$output\swagger.yaml" -Force -ErrorAction SilentlyContinue
|
||||
Write-Host " 已清理 swagger.json / swagger.yaml"
|
||||
|
||||
Write-Host "=== 3. 验证编译 ==="
|
||||
Set-Location $project
|
||||
go build ./...
|
||||
Write-Host "=== 完成 ===" -ForegroundColor Green
|
||||
181
sc-lktx-backend/scripts/import_employees.py
Normal file
181
sc-lktx-backend/scripts/import_employees.py
Normal file
@@ -0,0 +1,181 @@
|
||||
"""
|
||||
从通讯录 Excel 提取四川凌空员工数据 调用 admin API 导入
|
||||
用法: python import_employees.py <xlsx文件路径> [--dry-run]
|
||||
|
||||
需要管理员 JWT,三种方式:
|
||||
方式1:环境变量 ADMIN_TOKEN="xxx"
|
||||
方式2:环境变量 ADMIN_ACCOUNT="admin" ADMIN_PASSWORD="xxx"
|
||||
方式3:都不设则提示手动输入账号密码
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import json
|
||||
import requests
|
||||
import openpyxl
|
||||
|
||||
API_BASE = os.getenv("API_BASE", "http://localhost:28175/v1/admin")
|
||||
TOKEN = os.getenv("ADMIN_TOKEN", "")
|
||||
|
||||
def login_and_get_token():
|
||||
account = os.getenv("ADMIN_ACCOUNT", "")
|
||||
password = os.getenv("ADMIN_PASSWORD", "")
|
||||
if not account or not password:
|
||||
account = input("管理员账号: ").strip()
|
||||
password = input("管理员密码: ").strip()
|
||||
resp = requests.post(API_BASE.replace("/admin", "") + "/admin/login", json={
|
||||
"account": account,
|
||||
"password": password,
|
||||
})
|
||||
data = resp.json()
|
||||
if data.get("code") != 200:
|
||||
print(f"登录失败: {data.get('msg')}")
|
||||
sys.exit(1)
|
||||
token = data["data"]["token"]
|
||||
print(f" 登录成功")
|
||||
return token
|
||||
|
||||
def get_headers():
|
||||
global TOKEN
|
||||
if not TOKEN:
|
||||
TOKEN = login_and_get_token()
|
||||
return {"Authorization": f"Bearer {TOKEN}"}
|
||||
|
||||
def get_dept_map():
|
||||
"""从后台获取部门列表,建立 部门名称 ID 的映射"""
|
||||
resp = requests.get(f"{API_BASE}/departments-all", headers=get_headers())
|
||||
data = resp.json().get("data", [])
|
||||
print(f" 找到 {len(data)} 个部门")
|
||||
dept_map = {}
|
||||
for d in data:
|
||||
dept_map[d["name"]] = d["id"]
|
||||
return dept_map
|
||||
|
||||
def parse_excel(path):
|
||||
"""解析 Excel,返回所有员工列表(不过滤公司)"""
|
||||
wb = openpyxl.load_workbook(path, data_only=True)
|
||||
ws = wb.active
|
||||
|
||||
employees = []
|
||||
for row in ws.iter_rows(min_row=4, values_only=True):
|
||||
name = str(row[1] or "").strip()
|
||||
phone = str(row[2] or "").strip()
|
||||
dept_full = str(row[3] or "").strip()
|
||||
position = str(row[4] or "").strip()
|
||||
|
||||
if not name or not phone:
|
||||
continue
|
||||
|
||||
# 标准化手机号
|
||||
phone_clean = phone.replace("+86-", "").replace("+86", "").strip()
|
||||
|
||||
# 取第一个部门路径
|
||||
dept_first = dept_full.split(",")[0].strip() if dept_full else ""
|
||||
|
||||
employees.append({
|
||||
"real_name": name,
|
||||
"phone": phone_clean,
|
||||
"department_full": dept_first,
|
||||
"position": position,
|
||||
})
|
||||
|
||||
return employees
|
||||
|
||||
def match_department(dept_full, dept_map):
|
||||
"""尝试多种方式匹配部门"""
|
||||
# 1. 精确匹配完整名称
|
||||
if dept_full in dept_map:
|
||||
return dept_full, dept_map[dept_full]
|
||||
|
||||
# 2. 带分隔符的:取最后一级名
|
||||
for sep in ["-", "-", "", "/", ""]:
|
||||
if sep in dept_full:
|
||||
sub = dept_full.rsplit(sep, 1)[-1].strip()
|
||||
if sub in dept_map:
|
||||
return sub, dept_map[sub]
|
||||
|
||||
# 3. 模糊匹配:dept_map 中有包含关系的
|
||||
for name, did in dept_map.items():
|
||||
if name in dept_full or dept_full in name:
|
||||
return name, did
|
||||
|
||||
return None, None
|
||||
|
||||
def import_employees(employees, dept_map, dry_run=False):
|
||||
"""批量导入"""
|
||||
items = []
|
||||
skipped_no_dept = 0
|
||||
for emp in employees:
|
||||
matched_name, dept_id = match_department(emp["department_full"], dept_map)
|
||||
|
||||
if not dept_id:
|
||||
print(f" 跳过 {emp['real_name']}: 未匹配部门 '{emp['department_full']}'")
|
||||
skipped_no_dept += 1
|
||||
continue
|
||||
|
||||
items.append({
|
||||
"real_name": emp["real_name"],
|
||||
"phone": emp["phone"],
|
||||
"department_id": dept_id,
|
||||
"department_name": matched_name,
|
||||
"position": emp["position"],
|
||||
})
|
||||
|
||||
if dry_run:
|
||||
print(f"\n[dry-run] 将导入 {len(items)} 人,跳过 {skipped_no_dept} 人(无匹配部门)")
|
||||
for item in items[:5]:
|
||||
print(f" {item['real_name']} | {item['phone']} | {item['department_name']} | {item['position']}")
|
||||
if len(items) > 5:
|
||||
print(f" ... 共 {len(items)} 人")
|
||||
return
|
||||
|
||||
batch_size = 50
|
||||
total_imported = 0
|
||||
for i in range(0, len(items), batch_size):
|
||||
batch = items[i:i + batch_size]
|
||||
resp = requests.post(f"{API_BASE}/employees/import", json={"employees": batch}, headers=get_headers())
|
||||
result = resp.json()
|
||||
if result.get("code") == 200:
|
||||
data = result.get("data", {})
|
||||
total_imported += data.get("imported", 0)
|
||||
print(f" 批次 {i//batch_size + 1}: 导入 {data.get('imported')} / 跳过 {data.get('skipped')}")
|
||||
else:
|
||||
print(f" 批次 {i//batch_size + 1}: 失败 - {result.get('msg')}")
|
||||
|
||||
print(f"\n 共导入 {total_imported} 人")
|
||||
|
||||
if __name__ == "__main__":
|
||||
if len(sys.argv) < 2:
|
||||
print("用法: python import_employees.py <xlsx文件路径> [--dry-run] [--region 关键词]")
|
||||
print(" --region 关键词 只导入部门包含此关键词的员工(默认: 四川)")
|
||||
sys.exit(1)
|
||||
|
||||
path = sys.argv[1]
|
||||
dry_run = "--dry-run" in sys.argv
|
||||
|
||||
# 解析 region 参数
|
||||
region_filter = None
|
||||
for i, arg in enumerate(sys.argv):
|
||||
if arg == "--region" and i + 1 < len(sys.argv):
|
||||
region_filter = sys.argv[i + 1]
|
||||
if region_filter is None:
|
||||
region_filter = "四川" # 默认只导入四川
|
||||
|
||||
print(f" 获取部门列表...")
|
||||
dept_map = get_dept_map()
|
||||
|
||||
print("\n 解析 Excel...")
|
||||
employees = parse_excel(path)
|
||||
# 按区域过滤
|
||||
if region_filter:
|
||||
before = len(employees)
|
||||
employees = [e for e in employees if region_filter in e["department_full"]]
|
||||
print(f" 共 {before} 名员工,按区域 '{region_filter}' 过滤后: {len(employees)} 人")
|
||||
else:
|
||||
print(f" 提取到 {len(employees)} 名员工")
|
||||
|
||||
print("\n 导入中...")
|
||||
import_employees(employees, dept_map, dry_run)
|
||||
|
||||
if dry_run:
|
||||
print("\n 去掉 --dry-run 执行实际导入")
|
||||
Reference in New Issue
Block a user