Merge branch 'main' of ssh://121.41.67.237:10022/andy/by-crm

This commit is contained in:
hanshiyang 2026-01-27 15:20:19 +08:00
commit 264788b54c
19 changed files with 3025 additions and 118 deletions

31
.gitignore vendored
View File

@ -25,3 +25,34 @@
/backend/target/
/mysql/data/
# Maven
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
# IDE
.idea/
*.iml
.vscode/
.eclipse/
# Logs
*.log
logs/
# OS
.DS_Store
Thumbs.db
# Git
.git/
.gitignore
# Docker
Dockerfile
.dockerignore

View File

@ -12,7 +12,7 @@ COPY src ./src
RUN mvn clean package -DskipTests
# 运行阶段
FROM openjdk:8-jre-alpine
FROM eclipse-temurin:8-jre
WORKDIR /app

View File

@ -9,7 +9,7 @@ spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.3.80:3306/by_crm?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
url: jdbc:mysql://192.168.3.80:3306/crm_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
username: root
password: "Boyun@123"
druid:

View File

@ -17,11 +17,15 @@ services:
MYSQL_ROOT_PASSWORD: MySQL123s56
MYSQL_DATABASE: crm_db
TZ: Asia/Shanghai
command: [
'--character-set-server=utf8mb4',
'--collation-server=utf8mb4_unicode_ci',
]
ports:
- "39948:3306"
volumes:
- ./mysql/data:/var/lib/mysql
- ./sql/init.sql:/docker-entrypoint-initdb.d/init.sql:ro
- ./sql:/docker-entrypoint-initdb.d:ro
networks:
- crm_network
healthcheck:
@ -48,7 +52,7 @@ services:
SPRING_DATASOURCE_PASSWORD: MySQL123s56
# JWT 配置
JWT_SECRET: your-secret-key-change-in-production
JWT_SECRET: by-crm-jwt-secret-key-2024-hs512-requires-at-least-64-bytes-for-secure-signing-please-change-in-production-environment
JWT_EXPIRATION: 86400000
# 应用配置

Binary file not shown.

View File

@ -29,7 +29,6 @@ Thumbs.db
# Docker
Dockerfile
.dockerignore
nginx.conf
# Logs
*.log

View File

@ -32,7 +32,7 @@
"sass": "^1.69.0",
"typescript": "^5.3.0",
"vite": "^5.0.0",
"vue-tsc": "^1.8.0"
"vue-tsc": "^3.2.4"
},
"engines": {
"node": ">=16.0.0",

View File

@ -70,8 +70,8 @@ importers:
specifier: ^5.0.0
version: 5.4.21(@types/node@20.19.30)(sass@1.97.3)
vue-tsc:
specifier: ^1.8.0
version: 1.8.27(typescript@5.9.3)
specifier: ^3.2.4
version: 3.2.4(typescript@5.9.3)
packages:
@ -597,14 +597,14 @@ packages:
vite: ^5.0.0 || ^6.0.0
vue: ^3.2.25
'@volar/language-core@1.11.1':
resolution: {integrity: sha512-dOcNn3i9GgZAcJt43wuaEykSluAuOkQgzni1cuxLxTV0nJKanQztp7FxyswdRILaKH+P2XZMPRp2S4MV/pElCw==}
'@volar/language-core@2.4.27':
resolution: {integrity: sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==}
'@volar/source-map@1.11.1':
resolution: {integrity: sha512-hJnOnwZ4+WT5iupLRnuzbULZ42L7BWWPMmruzwtLhJfpDVoZLjNBxHDi2sY2bgZXCKlpU5XcsMFoYrsQmPhfZg==}
'@volar/source-map@2.4.27':
resolution: {integrity: sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==}
'@volar/typescript@1.11.1':
resolution: {integrity: sha512-iU+t2mas/4lYierSnoFOeRFQUhAEMgsFuQxoxvwn5EdQopw43j+J27a4lt9LMInx1gLJBC6qL14WYGlgymaSMQ==}
'@volar/typescript@2.4.27':
resolution: {integrity: sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==}
'@vue/compiler-core@3.5.27':
resolution: {integrity: sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==}
@ -621,13 +621,8 @@ packages:
'@vue/devtools-api@6.6.4':
resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==}
'@vue/language-core@1.8.27':
resolution: {integrity: sha512-L8Kc27VdQserNaCUNiSFdDl9LWT24ly8Hpwf1ECy3aFb9m6bDhBGQYOujDm21N7EW3moKIOKEanQwe1q5BK+mA==}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
'@vue/language-core@3.2.4':
resolution: {integrity: sha512-bqBGuSG4KZM45KKTXzGtoCl9cWju5jsaBKaJJe3h5hRAAWpZUuj5G+L+eI01sPIkm4H6setKRlw7E85wLdDNew==}
'@vue/reactivity@3.5.27':
resolution: {integrity: sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ==}
@ -668,6 +663,9 @@ packages:
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
alien-signals@3.1.2:
resolution: {integrity: sha512-d9dYqZTS90WLiU0I5c6DHj/HcKkF8ZyGN3G5x8wSbslulz70KOxaqCT0hQCo9KOyhVqzqGojvNdJXoTumZOtcw==}
ansi-regex@5.0.1:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
@ -739,9 +737,6 @@ packages:
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
engines: {node: '>= 0.8'}
computeds@0.0.1:
resolution: {integrity: sha512-7CEBgcMjVmitjYo5q8JTJVra6X5mQ20uTThdK+0kR7UEaDrAWEQcRiBtWJzga4eRpP6afNwwLsX2SET2JhVB1Q==}
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
@ -760,9 +755,6 @@ packages:
dayjs@1.11.19:
resolution: {integrity: sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==}
de-indent@1.0.2:
resolution: {integrity: sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==}
debug@4.4.3:
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
engines: {node: '>=6.0'}
@ -982,10 +974,6 @@ packages:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
he@1.2.0:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
hasBin: true
ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
@ -1100,15 +1088,11 @@ packages:
resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
engines: {node: '>=16 || 14 >=14.17'}
minimatch@9.0.5:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'}
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
muggle-string@0.3.1:
resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==}
muggle-string@0.4.1:
resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==}
nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
@ -1346,6 +1330,9 @@ packages:
terser:
optional: true
vscode-uri@3.1.0:
resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==}
vue-demi@0.14.10:
resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==}
engines: {node: '>=12'}
@ -1368,14 +1355,11 @@ packages:
peerDependencies:
vue: ^3.5.0
vue-template-compiler@2.7.16:
resolution: {integrity: sha512-AYbUWAJHLGGQM7+cNTELw+KsOG9nl2CnSv467WobS5Cv9uk3wFcnr1Etsz2sEIHEZvw1U+o9mRlEO6QbZvUPGQ==}
vue-tsc@1.8.27:
resolution: {integrity: sha512-WesKCAZCRAbmmhuGl3+VrdWItEvfoFIPXOvUJkjULi+x+6G/Dy69yO3TBRJDr9eUlmsNAwVmxsNZxvHKzbkKdg==}
vue-tsc@3.2.4:
resolution: {integrity: sha512-xj3YCvSLNDKt1iF9OcImWHhmYcihVu9p4b9s4PGR/qp6yhW+tZJaypGxHScRyOrdnHvaOeF+YkZOdKwbgGvp5g==}
hasBin: true
peerDependencies:
typescript: '*'
typescript: '>=5.0.0'
vue@3.5.27:
resolution: {integrity: sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==}
@ -1808,18 +1792,17 @@ snapshots:
vite: 5.4.21(@types/node@20.19.30)(sass@1.97.3)
vue: 3.5.27(typescript@5.9.3)
'@volar/language-core@1.11.1':
'@volar/language-core@2.4.27':
dependencies:
'@volar/source-map': 1.11.1
'@volar/source-map': 2.4.27
'@volar/source-map@1.11.1':
dependencies:
muggle-string: 0.3.1
'@volar/source-map@2.4.27': {}
'@volar/typescript@1.11.1':
'@volar/typescript@2.4.27':
dependencies:
'@volar/language-core': 1.11.1
'@volar/language-core': 2.4.27
path-browserify: 1.0.1
vscode-uri: 3.1.0
'@vue/compiler-core@3.5.27':
dependencies:
@ -1853,19 +1836,15 @@ snapshots:
'@vue/devtools-api@6.6.4': {}
'@vue/language-core@1.8.27(typescript@5.9.3)':
'@vue/language-core@3.2.4':
dependencies:
'@volar/language-core': 1.11.1
'@volar/source-map': 1.11.1
'@volar/language-core': 2.4.27
'@vue/compiler-dom': 3.5.27
'@vue/shared': 3.5.27
computeds: 0.0.1
minimatch: 9.0.5
muggle-string: 0.3.1
alien-signals: 3.1.2
muggle-string: 0.4.1
path-browserify: 1.0.1
vue-template-compiler: 2.7.16
optionalDependencies:
typescript: 5.9.3
picomatch: 4.0.3
'@vue/reactivity@3.5.27':
dependencies:
@ -1923,6 +1902,8 @@ snapshots:
json-schema-traverse: 0.4.1
uri-js: 4.4.1
alien-signals@3.1.2: {}
ansi-regex@5.0.1: {}
ansi-styles@4.3.0:
@ -1990,8 +1971,6 @@ snapshots:
dependencies:
delayed-stream: 1.0.0
computeds@0.0.1: {}
concat-map@0.0.1: {}
cross-spawn@7.0.6:
@ -2006,8 +1985,6 @@ snapshots:
dayjs@1.11.19: {}
de-indent@1.0.2: {}
debug@4.4.3:
dependencies:
ms: 2.1.3
@ -2302,8 +2279,6 @@ snapshots:
dependencies:
function-bind: 1.1.2
he@1.2.0: {}
ignore@5.3.2: {}
immutable@5.1.4: {}
@ -2398,13 +2373,9 @@ snapshots:
dependencies:
brace-expansion: 2.0.2
minimatch@9.0.5:
dependencies:
brace-expansion: 2.0.2
ms@2.1.3: {}
muggle-string@0.3.1: {}
muggle-string@0.4.1: {}
nanoid@3.3.11: {}
@ -2458,8 +2429,7 @@ snapshots:
picomatch@2.3.1: {}
picomatch@4.0.3:
optional: true
picomatch@4.0.3: {}
pinia-plugin-persistedstate@3.2.3(pinia@2.3.1(typescript@5.9.3)(vue@3.5.27(typescript@5.9.3))):
dependencies:
@ -2607,6 +2577,8 @@ snapshots:
fsevents: 2.3.3
sass: 1.97.3
vscode-uri@3.1.0: {}
vue-demi@0.14.10(vue@3.5.27(typescript@5.9.3)):
dependencies:
vue: 3.5.27(typescript@5.9.3)
@ -2629,16 +2601,10 @@ snapshots:
'@vue/devtools-api': 6.6.4
vue: 3.5.27(typescript@5.9.3)
vue-template-compiler@2.7.16:
vue-tsc@3.2.4(typescript@5.9.3):
dependencies:
de-indent: 1.0.2
he: 1.2.0
vue-tsc@1.8.27(typescript@5.9.3):
dependencies:
'@volar/typescript': 1.11.1
'@vue/language-core': 1.8.27(typescript@5.9.3)
semver: 7.7.3
'@volar/typescript': 2.4.27
'@vue/language-core': 3.2.4
typescript: 5.9.3
vue@3.5.27(typescript@5.9.3):

View File

@ -1,4 +1,4 @@
import request from '@/utils/request'
import { http } from '@/utils/request'
export interface DashboardStatistics {
customerCount: number
@ -11,8 +11,5 @@ export interface DashboardStatistics {
*
*/
export function getStatistics() {
return request<DashboardStatistics>({
url: '/dashboard/statistics',
method: 'get'
})
return http.get<DashboardStatistics>('/dashboard/statistics')
}

View File

@ -1,44 +1,30 @@
import request from '@/utils/request'
import { http } from '@/utils/request'
import type { SystemConfig } from '@/types'
/**
*
*/
export function getAllConfigs() {
return request<SystemConfig[]>({
url: '/system/config',
method: 'get'
})
return http.get<SystemConfig[]>('/system/config')
}
/**
*
*/
export function getConfigValue(configKey: string) {
return request<string>({
url: `/system/config/value/${configKey}`,
method: 'get'
})
return http.get<string>(`/system/config/value/${configKey}`)
}
/**
*
*/
export function updateConfig(configKey: string, configValue: string) {
return request({
url: '/system/config',
method: 'put',
data: { configKey, configValue }
})
return http.put('/system/config', { configKey, configValue })
}
/**
*
*/
export function batchUpdateConfigs(configs: Record<string, string>) {
return request({
url: '/system/config/batch',
method: 'put',
data: configs
})
return http.put('/system/config/batch', configs)
}

View File

@ -54,7 +54,7 @@ const router = createRouter({
})
// 路由守卫
router.beforeEach((to, from, next) => {
router.beforeEach((to, _from, next) => {
const userStore = useUserStore()
// 设置页面标题

View File

@ -1,6 +1,6 @@
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { login as loginApi, getUserInfo, logout as logoutApi } from '@/api/auth'
import { login as loginApi, getUserInfo } from '@/api/auth'
import type { LoginRequest, User } from '@/types'
export const useUserStore = defineStore(

View File

@ -168,7 +168,6 @@ const dialogVisible = ref(false)
const dialogTitle = computed(() => (formData.id ? '编辑客户' : '新增客户'))
const formRef = ref<FormInstance>()
const schoolSearchLoading = ref(false)
const schoolOptions = ref<School[]>([])
const queryForm = reactive({
current: 1,
@ -339,7 +338,7 @@ const handleDialogClose = () => {
const fetchProtectDays = async () => {
try {
const res = await getConfigValue('report.protect.days')
protectDays.value = parseInt(res) || 90
protectDays.value = parseInt(res, 10) || 90
} catch (error) {
console.error('获取配置失败', error)
}

View File

@ -181,7 +181,7 @@ const rules: FormRules = {
contactPhone: [{ required: true, message: '请输入联系电话', trigger: 'blur' }],
password: [
{
validator: (rule, value, callback) => {
validator: (_rule, value, callback) => {
//
if (value && (value.length < 6 || value.length > 20)) {
callback(new Error('密码长度必须在6-20位之间'))
@ -210,7 +210,7 @@ const resetPasswordRules: FormRules = {
confirmPassword: [
{ required: true, message: '请再次输入新密码', trigger: 'blur' },
{
validator: (rule, value, callback) => {
validator: (_rule, value, callback) => {
if (value !== resetPasswordForm.newPassword) {
callback(new Error('两次输入的密码不一致'))
} else {

View File

@ -144,7 +144,7 @@ const passwordRules: FormRules = {
confirmPassword: [
{ required: true, message: '请再次输入新密码', trigger: 'blur' },
{
validator: (rule, value, callback) => {
validator: (_rule, value, callback) => {
if (value !== passwordForm.newPassword) {
callback(new Error('两次输入的密码不一致'))
} else {

View File

@ -134,7 +134,7 @@
<!-- 审核对话框 -->
<el-dialog v-model="auditDialogVisible" title="审核报备" width="600px">
<el-form ref="auditFormRef" :model="auditForm" label-width="100px">
<el-form :model="auditForm" label-width="100px">
<el-form-item label="审核结果">
<el-radio-group v-model="auditForm.approved">
<el-radio :label="true">通过</el-radio>
@ -178,7 +178,6 @@ const dialogVisible = ref(false)
const auditDialogVisible = ref(false)
const auditReportId = ref<number>()
const formRef = ref<FormInstance>()
const auditFormRef = ref<FormInstance>()
const searchLoading = ref(false)
const customerOptions = ref<Array<{ id: number; name: string }>>([])

View File

@ -1,8 +1,11 @@
-- 经销商管理系统数据库初始化脚本
-- 数据库版本MySQL 8.0
CREATE DATABASE IF NOT EXISTS by_crm DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE by_crm;
CREATE DATABASE IF NOT EXISTS crm_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE crm_db;
SET NAMES utf8mb4;
SET CHARACTER SET utf8mb4;
-- 经销商表
CREATE TABLE IF NOT EXISTS crm_dealer (
@ -109,7 +112,7 @@ CREATE TABLE IF NOT EXISTS crm_operation_log (
-- 插入默认管理员用户名admin密码Bycrmadmin123BCrypt加密后的值
INSERT INTO crm_user (username, password, real_name, dealer_id, role, status) VALUES
('admin', '$10$kWExNPRis.HIzKDa112UZeq8jzGxI2tLFo0zTNRfxhyzk6MzMKPW6', '系统管理员', NULL, 0, 1);
('admin', '$2a$10$kWExNPRis.HIzKDa112UZeq8jzGxI2tLFo0zTNRfxhyzk6MzMKPW6', '系统管理员', NULL, 0, 1);
-- 插入数据字典
INSERT INTO crm_dict (dict_code, dict_name, description) VALUES

2924
sql/02_init_crm_school.sql Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1 +0,0 @@
/e/boyun-workspace/by-crm