upload_source

This commit is contained in:
hangyu.tao 2026-01-26 15:45:01 +08:00
parent 17752f0fd7
commit ceb0f74ccc
8 changed files with 113 additions and 12 deletions

View File

@ -285,6 +285,7 @@
<thead> <thead>
<tr> <tr>
<th>客户名称</th> <th>客户名称</th>
<th>来源</th>
<th>状态</th> <th>状态</th>
<th>开始时间</th> <th>开始时间</th>
<th>结束时间</th> <th>结束时间</th>
@ -791,6 +792,10 @@
<label for="trialCustomerInput">客户名称</label> <label for="trialCustomerInput">客户名称</label>
<input type="text" id="trialCustomerInput" name="customerName" placeholder="请输入客户名称" required> <input type="text" id="trialCustomerInput" name="customerName" placeholder="请输入客户名称" required>
</div> </div>
<div class="form-group">
<label for="trialCustomerSource">客户来源</label>
<input type="text" id="trialCustomerSource" name="source" placeholder="请输入客户来源(如:官网、展会、推荐等)">
</div>
<div class="form-group"> <div class="form-group">
<label>是否试用</label> <label>是否试用</label>
<div style="display: flex; gap: 20px; align-items: center;"> <div style="display: flex; gap: 20px; align-items: center;">
@ -837,6 +842,10 @@
<div class="modal-body"> <div class="modal-body">
<form id="editTrialPeriodForm"> <form id="editTrialPeriodForm">
<input type="hidden" id="editTrialPeriodId"> <input type="hidden" id="editTrialPeriodId">
<div class="form-group">
<label for="editTrialCustomerSource">客户来源</label>
<input type="text" id="editTrialCustomerSource" name="source" placeholder="请输入客户来源:如社区、销售、推荐等">
</div>
<div class="form-group"> <div class="form-group">
<label>是否试用</label> <label>是否试用</label>
<div style="display: flex; gap: 20px; align-items: center;"> <div style="display: flex; gap: 20px; align-items: center;">

View File

@ -274,7 +274,7 @@ function renderTrialPeriodsTable() {
if (filteredTrialPeriodsData.length === 0) { if (filteredTrialPeriodsData.length === 0) {
const row = document.createElement('tr'); const row = document.createElement('tr');
row.innerHTML = ` row.innerHTML = `
<td colspan="6" class="empty-state"> <td colspan="7" class="empty-state">
<i class="fas fa-users"></i> <i class="fas fa-users"></i>
<h3>👥 还没有客户试用信息</h3> <h3>👥 还没有客户试用信息</h3>
<p>点击上方添加试用时间开始管理客户试用</p> <p>点击上方添加试用时间开始管理客户试用</p>
@ -324,9 +324,11 @@ function renderTrialPeriodsTable() {
const startTime = formatDateTime(period.startTime); const startTime = formatDateTime(period.startTime);
const endTime = formatDateTime(period.endTime); const endTime = formatDateTime(period.endTime);
const createdAt = formatDateTime(period.createdAt); const createdAt = formatDateTime(period.createdAt);
const source = period.source || '';
row.innerHTML = ` row.innerHTML = `
<td><strong>${customerName}</strong></td> <td><strong>${customerName}</strong></td>
<td>${source}</td>
<td>${statusBadge}</td> <td>${statusBadge}</td>
<td>${startTime}</td> <td>${startTime}</td>
<td>${endTime}</td> <td>${endTime}</td>
@ -405,6 +407,9 @@ function openAddTrialModal() {
const inputEl = document.getElementById('trialCustomerInput'); const inputEl = document.getElementById('trialCustomerInput');
if (inputEl) inputEl.value = ''; if (inputEl) inputEl.value = '';
const sourceEl = document.getElementById('trialCustomerSource');
if (sourceEl) sourceEl.value = '';
document.querySelector('input[name="isTrial"][value="true"]').checked = true; document.querySelector('input[name="isTrial"][value="true"]').checked = true;
document.getElementById('trialStartTime').value = ''; document.getElementById('trialStartTime').value = '';
document.getElementById('trialEndTime').value = ''; document.getElementById('trialEndTime').value = '';
@ -418,6 +423,10 @@ function openEditTrialModal(periodId) {
document.getElementById('editTrialPeriodId').value = period.id; 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 startDate = new Date(period.startTime);
const endDate = new Date(period.endTime); const endDate = new Date(period.endTime);
@ -460,9 +469,12 @@ async function deleteTrialPeriodFromPage(periodId) {
// Create trial period from page // Create trial period from page
async function createTrialPeriodFromPage() { async function createTrialPeriodFromPage() {
const inputEl = document.getElementById('trialCustomerInput'); const inputEl = document.getElementById('trialCustomerInput');
const sourceEl = document.getElementById('trialCustomerSource');
// Get customer name from input // Get customer name from input
const customerName = inputEl ? inputEl.value.trim() : ''; 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 isTrialValue = document.querySelector('input[name="isTrial"]:checked').value;
const isTrial = isTrialValue === 'true'; const isTrial = isTrialValue === 'true';
@ -482,6 +494,7 @@ async function createTrialPeriodFromPage() {
// 直接使用 customerName不再需要查找或创建 customerId // 直接使用 customerName不再需要查找或创建 customerId
const formData = { const formData = {
customerName: customerName, customerName: customerName,
source: source,
startTime: new Date(startTime).toISOString(), startTime: new Date(startTime).toISOString(),
endTime: new Date(endTime).toISOString(), endTime: new Date(endTime).toISOString(),
isTrial: isTrial isTrial: isTrial

View File

@ -232,6 +232,10 @@ async function updateTrialPeriod() {
const startTime = document.getElementById('editTrialStartTime').value; const startTime = document.getElementById('editTrialStartTime').value;
const endTime = document.getElementById('editTrialEndTime').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 // Get isTrial value from radio buttons
const isTrialRadio = document.querySelector('input[name="editIsTrial"]:checked'); const isTrialRadio = document.querySelector('input[name="editIsTrial"]:checked');
const isTrial = isTrialRadio ? isTrialRadio.value === 'true' : true; const isTrial = isTrialRadio ? isTrialRadio.value === 'true' : true;
@ -242,6 +246,7 @@ async function updateTrialPeriod() {
} }
const formData = { const formData = {
source: source,
startTime: new Date(startTime).toISOString(), startTime: new Date(startTime).toISOString(),
endTime: new Date(endTime).toISOString(), endTime: new Date(endTime).toISOString(),
isTrial: isTrial isTrial: isTrial

View File

@ -84,6 +84,7 @@ func (h *TrialPeriodHandler) CreateTrialPeriod(w http.ResponseWriter, r *http.Re
trialPeriod := models.TrialPeriod{ trialPeriod := models.TrialPeriod{
CustomerName: req.CustomerName, CustomerName: req.CustomerName,
Source: req.Source,
StartTime: startTime, StartTime: startTime,
EndTime: endTime, EndTime: endTime,
IsTrial: req.IsTrial, IsTrial: req.IsTrial,

View File

@ -23,7 +23,7 @@ func NewMySQLTrialPeriodStorage() TrialPeriodStorage {
func (ts *mysqlTrialPeriodStorage) GetAllTrialPeriods() ([]models.TrialPeriod, error) { func (ts *mysqlTrialPeriodStorage) GetAllTrialPeriods() ([]models.TrialPeriod, error) {
query := ` 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 FROM trial_periods
ORDER BY created_at DESC ORDER BY created_at DESC
` `
@ -40,7 +40,7 @@ func (ts *mysqlTrialPeriodStorage) GetAllTrialPeriods() ([]models.TrialPeriod, e
var isTrial int var isTrial int
err := rows.Scan( err := rows.Scan(
&tp.ID, &tp.CustomerName, &tp.StartTime, &tp.ID, &tp.CustomerName, &tp.Source, &tp.StartTime,
&tp.EndTime, &isTrial, &tp.CreatedAt, &tp.EndTime, &isTrial, &tp.CreatedAt,
) )
if err != nil { if err != nil {
@ -56,7 +56,7 @@ func (ts *mysqlTrialPeriodStorage) GetAllTrialPeriods() ([]models.TrialPeriod, e
func (ts *mysqlTrialPeriodStorage) GetTrialPeriodsByCustomerID(customerID string) ([]models.TrialPeriod, error) { func (ts *mysqlTrialPeriodStorage) GetTrialPeriodsByCustomerID(customerID string) ([]models.TrialPeriod, error) {
query := ` 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 FROM trial_periods
WHERE customer_name = ? WHERE customer_name = ?
ORDER BY end_time DESC ORDER BY end_time DESC
@ -74,7 +74,7 @@ func (ts *mysqlTrialPeriodStorage) GetTrialPeriodsByCustomerID(customerID string
var isTrial int var isTrial int
err := rows.Scan( err := rows.Scan(
&tp.ID, &tp.CustomerName, &tp.StartTime, &tp.ID, &tp.CustomerName, &tp.Source, &tp.StartTime,
&tp.EndTime, &isTrial, &tp.CreatedAt, &tp.EndTime, &isTrial, &tp.CreatedAt,
) )
if err != nil { if err != nil {
@ -90,7 +90,7 @@ func (ts *mysqlTrialPeriodStorage) GetTrialPeriodsByCustomerID(customerID string
func (ts *mysqlTrialPeriodStorage) GetTrialPeriodByID(id string) (*models.TrialPeriod, error) { func (ts *mysqlTrialPeriodStorage) GetTrialPeriodByID(id string) (*models.TrialPeriod, error) {
query := ` 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 FROM trial_periods
WHERE id = ? WHERE id = ?
` `
@ -99,7 +99,7 @@ func (ts *mysqlTrialPeriodStorage) GetTrialPeriodByID(id string) (*models.TrialP
var isTrial int var isTrial int
err := ts.db.QueryRow(query, id).Scan( 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, &tp.EndTime, &isTrial, &tp.CreatedAt,
) )
@ -123,8 +123,8 @@ func (ts *mysqlTrialPeriodStorage) CreateTrialPeriod(trialPeriod models.TrialPer
} }
query := ` query := `
INSERT INTO trial_periods (id, customer_name, start_time, end_time, is_trial, created_at) INSERT INTO trial_periods (id, customer_name, source, start_time, end_time, is_trial, created_at)
VALUES (?, ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?, ?, ?)
` `
isTrial := 0 isTrial := 0
@ -133,7 +133,7 @@ func (ts *mysqlTrialPeriodStorage) CreateTrialPeriod(trialPeriod models.TrialPer
} }
_, err := ts.db.Exec(query, _, err := ts.db.Exec(query,
trialPeriod.ID, trialPeriod.CustomerName, trialPeriod.StartTime, trialPeriod.ID, trialPeriod.CustomerName, trialPeriod.Source, trialPeriod.StartTime,
trialPeriod.EndTime, isTrial, trialPeriod.CreatedAt, trialPeriod.EndTime, isTrial, trialPeriod.CreatedAt,
) )
@ -155,6 +155,9 @@ func (ts *mysqlTrialPeriodStorage) UpdateTrialPeriod(id string, updates models.U
if updates.CustomerName != nil { if updates.CustomerName != nil {
existing.CustomerName = *updates.CustomerName existing.CustomerName = *updates.CustomerName
} }
if updates.Source != nil {
existing.Source = *updates.Source
}
if updates.StartTime != nil { if updates.StartTime != nil {
startTime, err := time.Parse(time.RFC3339, *updates.StartTime) startTime, err := time.Parse(time.RFC3339, *updates.StartTime)
if err == nil { if err == nil {
@ -173,7 +176,7 @@ func (ts *mysqlTrialPeriodStorage) UpdateTrialPeriod(id string, updates models.U
query := ` query := `
UPDATE trial_periods UPDATE trial_periods
SET customer_name = ?, start_time = ?, end_time = ?, is_trial = ? SET customer_name = ?, source = ?, start_time = ?, end_time = ?, is_trial = ?
WHERE id = ? WHERE id = ?
` `
@ -183,7 +186,7 @@ func (ts *mysqlTrialPeriodStorage) UpdateTrialPeriod(id string, updates models.U
} }
_, err = ts.db.Exec(query, _, err = ts.db.Exec(query,
existing.CustomerName, existing.StartTime, existing.EndTime, existing.CustomerName, existing.Source, existing.StartTime, existing.EndTime,
isTrial, id, isTrial, id,
) )

View File

@ -136,6 +136,9 @@ func (ts *trialPeriodStorage) UpdateTrialPeriod(id string, updates models.Update
for i, period := range trialPeriods { for i, period := range trialPeriods {
if period.ID == id { if period.ID == id {
if updates.Source != nil {
trialPeriods[i].Source = *updates.Source
}
if updates.StartTime != nil { if updates.StartTime != nil {
startTime, err := time.Parse(time.RFC3339, *updates.StartTime) startTime, err := time.Parse(time.RFC3339, *updates.StartTime)
if err == nil { if err == nil {

View File

@ -6,6 +6,7 @@ import "time"
type TrialPeriod struct { type TrialPeriod struct {
ID string `json:"id"` ID string `json:"id"`
CustomerName string `json:"customerName"` // 直接存储客户名称 CustomerName string `json:"customerName"` // 直接存储客户名称
Source string `json:"source"` // 客户来源
StartTime time.Time `json:"startTime"` StartTime time.Time `json:"startTime"`
EndTime time.Time `json:"endTime"` EndTime time.Time `json:"endTime"`
IsTrial bool `json:"isTrial"` IsTrial bool `json:"isTrial"`
@ -15,6 +16,7 @@ type TrialPeriod struct {
// CreateTrialPeriodRequest represents the request to create a trial period // CreateTrialPeriodRequest represents the request to create a trial period
type CreateTrialPeriodRequest struct { type CreateTrialPeriodRequest struct {
CustomerName string `json:"customerName"` // 直接使用客户名称 CustomerName string `json:"customerName"` // 直接使用客户名称
Source string `json:"source"` // 客户来源
StartTime string `json:"startTime"` StartTime string `json:"startTime"`
EndTime string `json:"endTime"` EndTime string `json:"endTime"`
IsTrial bool `json:"isTrial"` IsTrial bool `json:"isTrial"`
@ -23,6 +25,7 @@ type CreateTrialPeriodRequest struct {
// UpdateTrialPeriodRequest represents the request to update a trial period // UpdateTrialPeriodRequest represents the request to update a trial period
type UpdateTrialPeriodRequest struct { type UpdateTrialPeriodRequest struct {
CustomerName *string `json:"customerName,omitempty"` CustomerName *string `json:"customerName,omitempty"`
Source *string `json:"source,omitempty"` // 客户来源
StartTime *string `json:"startTime,omitempty"` StartTime *string `json:"startTime,omitempty"`
EndTime *string `json:"endTime,omitempty"` EndTime *string `json:"endTime,omitempty"`
IsTrial *bool `json:"isTrial,omitempty"` IsTrial *bool `json:"isTrial,omitempty"`

View File

@ -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
}