企迹(qiji) 政企周报管理系统 — v0.1

后端: FastAPI + SQLAlchemy 2.0 (async) + Alembic + MinIO + Casdoor + 企微
前端: Vue 3 + Vite + TypeScript + Element Plus + Pinia

功能清单:
- 8 张数据表自动建表 / Casdoor OIDC 登录 / 企微静默登录
- 双布局: 移动端(填报) + PC端(汇总管理)
- 拜访记录 CRUD + MinIO 照片直传 + 缩略图预览 + 同访人草稿
- 今日纪要 (6 分类) / 工作计划 / 小微商机 / 要客拜访 CRUD
- 客户档案: 备注/收支费用/联系人/归属分配/批量转移
- 客户导入导出 + 模板下载 + 搜索/分页/筛选
- 仪表盘: 四卡统计 + 填报进度 (拜访+纪要双维度)
- 周报详情: 五 Tab + 按人/客户筛选 + 时间轴
- 用户管理 / 客户经理 PC 端工作台
- 企微: 催办/公告/定时提醒 / 时区修正
- Docker 部署配置

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-06-23 01:35:46 +08:00
parent 2a8225c31f
commit a1886074dd
97 changed files with 8830 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
import api from './index'
export const authApi = {
casdoorLogin(code: string, state: string) {
return api.post('/auth/casdoor-login', { code, state })
},
wecomLogin(code: string) {
return api.post('/auth/wecom-login', { code })
},
bindWecom(casdoorCode: string, wecomUserid: string) {
return api.post('/auth/bind-wecom', { casdoor_code: casdoorCode, wecom_userid: wecomUserid })
},
}
+32
View File
@@ -0,0 +1,32 @@
import api from './index'
export const customersApi = {
list(params?: any) {
return api.get('/customers/', { params })
},
get(id: string) {
return api.get(`/customers/${id}`)
},
create(data: any) {
return api.post('/customers/', data)
},
update(id: string, data: any) {
return api.put(`/customers/${id}`, data)
},
delete(id: string) {
return api.delete(`/customers/${id}`)
},
checkDuplicate(name: string) {
return api.get(`/customers/check-duplicate/${encodeURIComponent(name)}`)
},
// Assignments
listAssignments(customerId: string) {
return api.get(`/customers/${customerId}/assignments`)
},
assignManager(customerId: string, data: any) {
return api.post(`/customers/${customerId}/assignments`, data)
},
batchAssign(data: any) {
return api.post('/customers/batch-assign', data)
},
}
+13
View File
@@ -0,0 +1,13 @@
import api from './index'
export const dashboardApi = {
getStats() {
return api.get('/dashboard/stats')
},
getProgress() {
return api.get('/dashboard/progress')
},
getWeeklyReport(params?: any) {
return api.get('/dashboard/weekly-report', { params })
},
}
+30
View File
@@ -0,0 +1,30 @@
import axios from 'axios'
import { useAuthStore } from '@/stores/auth'
import router from '@/router'
const api = axios.create({
baseURL: '/api',
timeout: 30000,
})
api.interceptors.request.use((config) => {
const auth = useAuthStore()
if (auth.token) {
config.headers.Authorization = `Bearer ${auth.token}`
}
return config
})
api.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
const auth = useAuthStore()
auth.logout()
router.push('/login')
}
return Promise.reject(error)
}
)
export default api
+16
View File
@@ -0,0 +1,16 @@
import api from './index'
export const keyVisitsApi = {
list(params?: any) {
return api.get('/key-visits/', { params })
},
create(data: any) {
return api.post('/key-visits/', data)
},
update(id: string, data: any) {
return api.put(`/key-visits/${id}`, data)
},
delete(id: string) {
return api.delete(`/key-visits/${id}`)
},
}
+16
View File
@@ -0,0 +1,16 @@
import api from './index'
export const miniBusinessApi = {
list(params?: any) {
return api.get('/mini-business/', { params })
},
create(data: any) {
return api.post('/mini-business/', data)
},
update(id: string, data: any) {
return api.put(`/mini-business/${id}`, data)
},
delete(id: string) {
return api.delete(`/mini-business/${id}`)
},
}
+20
View File
@@ -0,0 +1,20 @@
import api from './index'
import axios from 'axios'
export const uploadApi = {
async getPresignedUrl(filename: string, contentType: string = 'image/jpeg') {
return api.post('/upload/presigned-url', null, {
params: { filename, content_type: contentType },
})
},
getDownloadUrl(objectKey: string) {
return api.get('/upload/download-url', { params: { object_key: objectKey } })
},
// Direct upload to MinIO
async uploadFile(uploadUrl: string, file: File) {
return axios.put(uploadUrl, file, {
headers: { 'Content-Type': file.type },
timeout: 60000,
})
},
}
+22
View File
@@ -0,0 +1,22 @@
import api from './index'
export const visitsApi = {
list(params?: any) {
return api.get('/visits/', { params })
},
getToday() {
return api.get('/visits/today')
},
get(id: string) {
return api.get(`/visits/${id}`)
},
create(data: any) {
return api.post('/visits/', data)
},
update(id: string, data: any) {
return api.put(`/visits/${id}`, data)
},
delete(id: string) {
return api.delete(`/visits/${id}`)
},
}
+16
View File
@@ -0,0 +1,16 @@
import api from './index'
export const workPlansApi = {
list(params?: any) {
return api.get('/work-plans/', { params })
},
create(data: any) {
return api.post('/work-plans/', data)
},
update(id: string, data: any) {
return api.put(`/work-plans/${id}`, data)
},
delete(id: string) {
return api.delete(`/work-plans/${id}`)
},
}