diff --git a/frontend/js/trial-periods-page.js b/frontend/js/trial-periods-page.js
index bd4da3d..8995b91 100644
--- a/frontend/js/trial-periods-page.js
+++ b/frontend/js/trial-periods-page.js
@@ -274,7 +274,7 @@ function renderTrialPeriodsTable() {
if (filteredTrialPeriodsData.length === 0) {
const row = document.createElement('tr');
row.innerHTML = `
-
+ |
👥 还没有客户试用信息
点击上方「添加试用时间」开始管理客户试用
@@ -324,9 +324,11 @@ function renderTrialPeriodsTable() {
const startTime = formatDateTime(period.startTime);
const endTime = formatDateTime(period.endTime);
const createdAt = formatDateTime(period.createdAt);
+ const source = period.source || '';
row.innerHTML = `
| ${customerName} |
+
${source} |
${statusBadge} |
${startTime} |
${endTime} |
@@ -405,6 +407,9 @@ function openAddTrialModal() {
const inputEl = document.getElementById('trialCustomerInput');
if (inputEl) inputEl.value = '';
+ const sourceEl = document.getElementById('trialCustomerSource');
+ if (sourceEl) sourceEl.value = '';
+
document.querySelector('input[name="isTrial"][value="true"]').checked = true;
document.getElementById('trialStartTime').value = '';
document.getElementById('trialEndTime').value = '';
@@ -418,6 +423,10 @@ function openEditTrialModal(periodId) {
document.getElementById('editTrialPeriodId').value = period.id;
+ // Set customer source
+ const sourceEl = document.getElementById('editTrialCustomerSource');
+ if (sourceEl) sourceEl.value = period.source || '';
+
const startDate = new Date(period.startTime);
const endDate = new Date(period.endTime);
@@ -460,9 +469,12 @@ async function deleteTrialPeriodFromPage(periodId) {
// Create trial period from page
async function createTrialPeriodFromPage() {
const inputEl = document.getElementById('trialCustomerInput');
+ const sourceEl = document.getElementById('trialCustomerSource');
// Get customer name from input
const customerName = inputEl ? inputEl.value.trim() : '';
+ // Get customer source from input
+ const source = sourceEl ? sourceEl.value.trim() : '';
const isTrialValue = document.querySelector('input[name="isTrial"]:checked').value;
const isTrial = isTrialValue === 'true';
@@ -482,6 +494,7 @@ async function createTrialPeriodFromPage() {
// 直接使用 customerName,不再需要查找或创建 customerId
const formData = {
customerName: customerName,
+ source: source,
startTime: new Date(startTime).toISOString(),
endTime: new Date(endTime).toISOString(),
isTrial: isTrial
diff --git a/frontend/js/trial-periods.js b/frontend/js/trial-periods.js
index 8cab91b..8512c1a 100644
--- a/frontend/js/trial-periods.js
+++ b/frontend/js/trial-periods.js
@@ -232,6 +232,10 @@ async function updateTrialPeriod() {
const startTime = document.getElementById('editTrialStartTime').value;
const endTime = document.getElementById('editTrialEndTime').value;
+ // Get source value
+ const sourceEl = document.getElementById('editTrialCustomerSource');
+ const source = sourceEl ? sourceEl.value.trim() : '';
+
// Get isTrial value from radio buttons
const isTrialRadio = document.querySelector('input[name="editIsTrial"]:checked');
const isTrial = isTrialRadio ? isTrialRadio.value === 'true' : true;
@@ -242,6 +246,7 @@ async function updateTrialPeriod() {
}
const formData = {
+ source: source,
startTime: new Date(startTime).toISOString(),
endTime: new Date(endTime).toISOString(),
isTrial: isTrial
diff --git a/internal/handlers/trial_period_handler.go b/internal/handlers/trial_period_handler.go
index 0a48781..14eb7be 100644
--- a/internal/handlers/trial_period_handler.go
+++ b/internal/handlers/trial_period_handler.go
@@ -84,6 +84,7 @@ func (h *TrialPeriodHandler) CreateTrialPeriod(w http.ResponseWriter, r *http.Re
trialPeriod := models.TrialPeriod{
CustomerName: req.CustomerName,
+ Source: req.Source,
StartTime: startTime,
EndTime: endTime,
IsTrial: req.IsTrial,
diff --git a/internal/storage/mysql_trial_period_storage.go b/internal/storage/mysql_trial_period_storage.go
index bbcfda6..043ff90 100644
--- a/internal/storage/mysql_trial_period_storage.go
+++ b/internal/storage/mysql_trial_period_storage.go
@@ -23,7 +23,7 @@ func NewMySQLTrialPeriodStorage() TrialPeriodStorage {
func (ts *mysqlTrialPeriodStorage) GetAllTrialPeriods() ([]models.TrialPeriod, error) {
query := `
- SELECT id, customer_name, start_time, end_time, is_trial, created_at
+ SELECT id, customer_name, COALESCE(source, '') as source, start_time, end_time, is_trial, created_at
FROM trial_periods
ORDER BY created_at DESC
`
@@ -40,7 +40,7 @@ func (ts *mysqlTrialPeriodStorage) GetAllTrialPeriods() ([]models.TrialPeriod, e
var isTrial int
err := rows.Scan(
- &tp.ID, &tp.CustomerName, &tp.StartTime,
+ &tp.ID, &tp.CustomerName, &tp.Source, &tp.StartTime,
&tp.EndTime, &isTrial, &tp.CreatedAt,
)
if err != nil {
@@ -56,7 +56,7 @@ func (ts *mysqlTrialPeriodStorage) GetAllTrialPeriods() ([]models.TrialPeriod, e
func (ts *mysqlTrialPeriodStorage) GetTrialPeriodsByCustomerID(customerID string) ([]models.TrialPeriod, error) {
query := `
- SELECT id, customer_name, start_time, end_time, is_trial, created_at
+ SELECT id, customer_name, COALESCE(source, '') as source, start_time, end_time, is_trial, created_at
FROM trial_periods
WHERE customer_name = ?
ORDER BY end_time DESC
@@ -74,7 +74,7 @@ func (ts *mysqlTrialPeriodStorage) GetTrialPeriodsByCustomerID(customerID string
var isTrial int
err := rows.Scan(
- &tp.ID, &tp.CustomerName, &tp.StartTime,
+ &tp.ID, &tp.CustomerName, &tp.Source, &tp.StartTime,
&tp.EndTime, &isTrial, &tp.CreatedAt,
)
if err != nil {
@@ -90,7 +90,7 @@ func (ts *mysqlTrialPeriodStorage) GetTrialPeriodsByCustomerID(customerID string
func (ts *mysqlTrialPeriodStorage) GetTrialPeriodByID(id string) (*models.TrialPeriod, error) {
query := `
- SELECT id, customer_name, start_time, end_time, is_trial, created_at
+ SELECT id, customer_name, COALESCE(source, '') as source, start_time, end_time, is_trial, created_at
FROM trial_periods
WHERE id = ?
`
@@ -99,7 +99,7 @@ func (ts *mysqlTrialPeriodStorage) GetTrialPeriodByID(id string) (*models.TrialP
var isTrial int
err := ts.db.QueryRow(query, id).Scan(
- &tp.ID, &tp.CustomerName, &tp.StartTime,
+ &tp.ID, &tp.CustomerName, &tp.Source, &tp.StartTime,
&tp.EndTime, &isTrial, &tp.CreatedAt,
)
@@ -123,8 +123,8 @@ func (ts *mysqlTrialPeriodStorage) CreateTrialPeriod(trialPeriod models.TrialPer
}
query := `
- INSERT INTO trial_periods (id, customer_name, start_time, end_time, is_trial, created_at)
- VALUES (?, ?, ?, ?, ?, ?)
+ INSERT INTO trial_periods (id, customer_name, source, start_time, end_time, is_trial, created_at)
+ VALUES (?, ?, ?, ?, ?, ?, ?)
`
isTrial := 0
@@ -133,7 +133,7 @@ func (ts *mysqlTrialPeriodStorage) CreateTrialPeriod(trialPeriod models.TrialPer
}
_, err := ts.db.Exec(query,
- trialPeriod.ID, trialPeriod.CustomerName, trialPeriod.StartTime,
+ trialPeriod.ID, trialPeriod.CustomerName, trialPeriod.Source, trialPeriod.StartTime,
trialPeriod.EndTime, isTrial, trialPeriod.CreatedAt,
)
@@ -155,6 +155,9 @@ func (ts *mysqlTrialPeriodStorage) UpdateTrialPeriod(id string, updates models.U
if updates.CustomerName != nil {
existing.CustomerName = *updates.CustomerName
}
+ if updates.Source != nil {
+ existing.Source = *updates.Source
+ }
if updates.StartTime != nil {
startTime, err := time.Parse(time.RFC3339, *updates.StartTime)
if err == nil {
@@ -173,7 +176,7 @@ func (ts *mysqlTrialPeriodStorage) UpdateTrialPeriod(id string, updates models.U
query := `
UPDATE trial_periods
- SET customer_name = ?, start_time = ?, end_time = ?, is_trial = ?
+ SET customer_name = ?, source = ?, start_time = ?, end_time = ?, is_trial = ?
WHERE id = ?
`
@@ -183,7 +186,7 @@ func (ts *mysqlTrialPeriodStorage) UpdateTrialPeriod(id string, updates models.U
}
_, err = ts.db.Exec(query,
- existing.CustomerName, existing.StartTime, existing.EndTime,
+ existing.CustomerName, existing.Source, existing.StartTime, existing.EndTime,
isTrial, id,
)
diff --git a/internal/storage/trial_period_storage.go b/internal/storage/trial_period_storage.go
index 009bc00..92419c5 100644
--- a/internal/storage/trial_period_storage.go
+++ b/internal/storage/trial_period_storage.go
@@ -136,6 +136,9 @@ func (ts *trialPeriodStorage) UpdateTrialPeriod(id string, updates models.Update
for i, period := range trialPeriods {
if period.ID == id {
+ if updates.Source != nil {
+ trialPeriods[i].Source = *updates.Source
+ }
if updates.StartTime != nil {
startTime, err := time.Parse(time.RFC3339, *updates.StartTime)
if err == nil {
diff --git a/models/trial_period.go b/models/trial_period.go
index 459aa18..84ae6d0 100644
--- a/models/trial_period.go
+++ b/models/trial_period.go
@@ -6,6 +6,7 @@ import "time"
type TrialPeriod struct {
ID string `json:"id"`
CustomerName string `json:"customerName"` // 直接存储客户名称
+ Source string `json:"source"` // 客户来源
StartTime time.Time `json:"startTime"`
EndTime time.Time `json:"endTime"`
IsTrial bool `json:"isTrial"`
@@ -15,6 +16,7 @@ type TrialPeriod struct {
// CreateTrialPeriodRequest represents the request to create a trial period
type CreateTrialPeriodRequest struct {
CustomerName string `json:"customerName"` // 直接使用客户名称
+ Source string `json:"source"` // 客户来源
StartTime string `json:"startTime"`
EndTime string `json:"endTime"`
IsTrial bool `json:"isTrial"`
@@ -23,6 +25,7 @@ type CreateTrialPeriodRequest struct {
// UpdateTrialPeriodRequest represents the request to update a trial period
type UpdateTrialPeriodRequest struct {
CustomerName *string `json:"customerName,omitempty"`
+ Source *string `json:"source,omitempty"` // 客户来源
StartTime *string `json:"startTime,omitempty"`
EndTime *string `json:"endTime,omitempty"`
IsTrial *bool `json:"isTrial,omitempty"`
diff --git a/tools/add_source_column.go b/tools/add_source_column.go
new file mode 100644
index 0000000..5cac746
--- /dev/null
+++ b/tools/add_source_column.go
@@ -0,0 +1,64 @@
+package main
+
+import (
+ "database/sql"
+ "fmt"
+ "log"
+ "os"
+
+ _ "github.com/go-sql-driver/mysql"
+)
+
+func main() {
+ // 从环境变量或默认值获取数据库配置
+ host := getEnv("DB_HOST", "mysql1.rdsmbk3ednsgnnt.rds.bj.baidubce.com")
+ port := getEnv("DB_PORT", "3306")
+ user := getEnv("DB_USER", "root_dev")
+ password := getEnv("DB_PASSWORD", "Kdse89sd")
+ dbname := getEnv("DB_NAME", "crm_db")
+
+ dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?parseTime=true", user, password, host, port, dbname)
+
+ db, err := sql.Open("mysql", dsn)
+ if err != nil {
+ log.Fatalf("连接数据库失败: %v", err)
+ }
+ defer db.Close()
+
+ if err := db.Ping(); err != nil {
+ log.Fatalf("数据库连接测试失败: %v", err)
+ }
+ log.Println("✅ 数据库连接成功")
+
+ // 检查 source 列是否存在
+ var columnExists bool
+ err = db.QueryRow(`
+ SELECT COUNT(*) > 0
+ FROM information_schema.COLUMNS
+ WHERE TABLE_SCHEMA = ? AND TABLE_NAME = 'trial_periods' AND COLUMN_NAME = 'source'
+ `, dbname).Scan(&columnExists)
+
+ if err != nil {
+ log.Fatalf("检查列是否存在失败: %v", err)
+ }
+
+ if columnExists {
+ log.Println("✅ source 列已存在,无需迁移")
+ return
+ }
+
+ // 添加 source 列
+ _, err = db.Exec(`ALTER TABLE trial_periods ADD COLUMN source VARCHAR(255) DEFAULT '' AFTER customer_name`)
+ if err != nil {
+ log.Fatalf("添加 source 列失败: %v", err)
+ }
+
+ log.Println("✅ 成功添加 source (客户来源) 列到 trial_periods 表")
+}
+
+func getEnv(key, defaultValue string) string {
+ if value := os.Getenv(key); value != "" {
+ return value
+ }
+ return defaultValue
+}