diff --git a/.mimocode/login-payload.json b/.mimocode/login-payload.json
index c910f3b..e69de29 100644
--- a/.mimocode/login-payload.json
+++ b/.mimocode/login-payload.json
@@ -1 +0,0 @@
-{"tenant_name":"连云港云泽广告传媒有限公司","account":"hero920103","password":"920103","lot_number":"test","pass_token":"test","gen_time":"test","captcha_output":"test"}
diff --git a/backend/src/components/CommonHeader.vue b/backend/src/components/CommonHeader.vue
index 68ab38b..1d74c37 100644
--- a/backend/src/components/CommonHeader.vue
+++ b/backend/src/components/CommonHeader.vue
@@ -7,7 +7,7 @@
- 云泽管理平台
+ 温新云平台
{{ currentModule?.title || "数据赋能管理 · 科技创造价值" }}
diff --git a/backend/src/views/apps/oa/schedule/index.vue b/backend/src/views/apps/oa/schedule/index.vue
index 7fb0ee8..8bd016e 100644
--- a/backend/src/views/apps/oa/schedule/index.vue
+++ b/backend/src/views/apps/oa/schedule/index.vue
@@ -1378,7 +1378,13 @@ onMounted(() => {
}
.calendar-cell:hover {
- background: #f5f8ff;
+ background: repeating-linear-gradient(
+ 135deg,
+ #fafcff 0,
+ #fafcff 6px,
+ #edf4ff 6px,
+ #edf4ff 12px
+ );
}
.calendar-cell.other-month {
@@ -1386,6 +1392,16 @@ onMounted(() => {
background: #fafbfc;
}
+.calendar-cell.other-month:hover {
+ background: repeating-linear-gradient(
+ 135deg,
+ #fafbfc 0,
+ #fafbfc 6px,
+ #f2f4f8 6px,
+ #f2f4f8 12px
+ );
+}
+
.cell-date {
display: flex;
align-items: center;
@@ -1435,7 +1451,13 @@ onMounted(() => {
}
.calendar-cell.is-off:hover {
- background: #fff5f5;
+ background: repeating-linear-gradient(
+ 135deg,
+ #fff7f7 0,
+ #fff7f7 7px,
+ #fde8e8 7px,
+ #fde8e8 14px
+ );
}
/* 调休上班日的周六日按工作日处理,弱化底色避免误判为休息 */
@@ -1443,6 +1465,16 @@ onMounted(() => {
background: #fff;
}
+.calendar-cell.is-work:hover {
+ background: repeating-linear-gradient(
+ 135deg,
+ #ffffff 0,
+ #ffffff 6px,
+ #f2f5f9 6px,
+ #f2f5f9 12px
+ );
+}
+
/* 我的排班背景:上班绿 / 休息橙。排班是实际作息,覆盖节假日底色,
但保留休/班角标与节日名;today / selected 在后面定义,优先级仍最高。
仅靠提高底色饱和度区分,不加边框/竖条 */
@@ -1451,7 +1483,13 @@ onMounted(() => {
}
.calendar-cell.is-my-work:hover {
- background: #d3f0bd;
+ background: repeating-linear-gradient(
+ 135deg,
+ #e2f6d2 0,
+ #e2f6d2 7px,
+ #d2efbe 7px,
+ #d2efbe 14px
+ );
}
/* 休息日:斜纹底 + 右上角「休」角标。
@@ -1514,6 +1552,16 @@ onMounted(() => {
);
box-shadow: inset 0 0 0 2px #409eff;
border-radius: 4px;
+
+ &:hover {
+ background: repeating-linear-gradient(
+ 135deg,
+ #e0efff 0,
+ #e0efff 6px,
+ #cbe4ff 6px,
+ #cbe4ff 12px
+ );
+ }
}
/* 今天:不再用实底蓝,改为绿色描边框,保留排班底色(上班绿 / 休息斜纹)不变。
@@ -1830,21 +1878,27 @@ html.dark & {
border-bottom-color: var(--el-border-color-lighter);
&:hover {
- background: var(--el-fill-color-light);
+ background: repeating-linear-gradient(
+ 135deg,
+ rgba(255, 255, 255, 0.03) 0,
+ rgba(255, 255, 255, 0.03) 6px,
+ rgba(255, 255, 255, 0.08) 6px,
+ rgba(255, 255, 255, 0.08) 12px
+ );
}
&.other-month {
color: var(--el-text-color-placeholder);
background: var(--el-fill-color-light);
- }
- &.selected {
- background: repeating-linear-gradient(
- 135deg,
- rgba(64, 158, 255, 0.14) 0,
- rgba(64, 158, 255, 0.14) 6px,
- rgba(64, 158, 255, 0.3) 6px,
- rgba(64, 158, 255, 0.3) 12px
- );
- box-shadow: inset 0 0 0 2px var(--el-color-primary);
+
+ &:hover {
+ background: repeating-linear-gradient(
+ 135deg,
+ rgba(255, 255, 255, 0.02) 0,
+ rgba(255, 255, 255, 0.02) 6px,
+ rgba(255, 255, 255, 0.06) 6px,
+ rgba(255, 255, 255, 0.06) 12px
+ );
+ }
}
}
.month-tag {
@@ -1865,24 +1919,80 @@ html.dark & {
background: rgba(245, 108, 108, 0.07);
&:hover {
- background: rgba(245, 108, 108, 0.12);
+ background: repeating-linear-gradient(
+ 135deg,
+ rgba(245, 108, 108, 0.06) 0,
+ rgba(245, 108, 108, 0.06) 7px,
+ rgba(245, 108, 108, 0.16) 7px,
+ rgba(245, 108, 108, 0.16) 14px
+ );
}
}
.calendar-cell.is-work {
background: var(--el-bg-color);
+
+ &:hover {
+ background: repeating-linear-gradient(
+ 135deg,
+ rgba(255, 255, 255, 0.03) 0,
+ rgba(255, 255, 255, 0.03) 6px,
+ rgba(255, 255, 255, 0.08) 6px,
+ rgba(255, 255, 255, 0.08) 12px
+ );
+ }
}
.calendar-cell.is-my-work {
background: rgba(103, 194, 58, 0.1);
&:hover {
- background: rgba(103, 194, 58, 0.16);
+ background: repeating-linear-gradient(
+ 135deg,
+ rgba(103, 194, 58, 0.08) 0,
+ rgba(103, 194, 58, 0.08) 7px,
+ rgba(103, 194, 58, 0.2) 7px,
+ rgba(103, 194, 58, 0.2) 14px
+ );
}
}
+ /* 休息日:保持浅色主题的斜纹语言,仅用半透明琥珀色适配深色底,斜纹在暗底仍可辨 */
.calendar-cell.is-my-rest {
- background: rgba(230, 162, 60, 0.1);
+ background: repeating-linear-gradient(
+ 135deg,
+ rgba(230, 162, 60, 0.07) 0,
+ rgba(230, 162, 60, 0.07) 7px,
+ rgba(230, 162, 60, 0.16) 7px,
+ rgba(230, 162, 60, 0.16) 14px
+ );
&:hover {
- background: rgba(230, 162, 60, 0.16);
+ background: repeating-linear-gradient(
+ 135deg,
+ rgba(230, 162, 60, 0.12) 0,
+ rgba(230, 162, 60, 0.12) 7px,
+ rgba(230, 162, 60, 0.24) 7px,
+ rgba(230, 162, 60, 0.24) 14px
+ );
+ }
+ }
+ /* 选中态:暗色下优先级高于所有排班与节假日底色,保持蓝色斜条纹与主色描边 */
+ .calendar-cell.selected {
+ background: repeating-linear-gradient(
+ 135deg,
+ rgba(64, 158, 255, 0.15) 0,
+ rgba(64, 158, 255, 0.15) 6px,
+ rgba(64, 158, 255, 0.32) 6px,
+ rgba(64, 158, 255, 0.32) 12px
+ );
+ box-shadow: inset 0 0 0 2px var(--el-color-primary);
+
+ &:hover {
+ background: repeating-linear-gradient(
+ 135deg,
+ rgba(64, 158, 255, 0.24) 0,
+ rgba(64, 158, 255, 0.24) 6px,
+ rgba(64, 158, 255, 0.44) 6px,
+ rgba(64, 158, 255, 0.44) 12px
+ );
}
}
/* 今天:与浅色主题一致,只用绿色描边框,不覆盖排班底色 */
diff --git a/backend/src/views/basicSettings/applies/index.vue b/backend/src/views/basicSettings/applies/index.vue
index d5e861e..a99f706 100644
--- a/backend/src/views/basicSettings/applies/index.vue
+++ b/backend/src/views/basicSettings/applies/index.vue
@@ -46,6 +46,15 @@
{{ scope.row.identity_mobile || "" }}
+
+
+ {{ scope.row.applicant_name || "-" }}
+
+ {{ scope.row.department_name || "-" }} · {{ scope.row.position_name || "-" }}
+
+ 微信手机号 {{ scope.row.wechat_mobile || "-" }}
+
+
{{ scope.row.contact_person || "-" }}
diff --git a/backend/src/views/home/index.vue b/backend/src/views/home/index.vue
index f1b6a20..29a17ba 100644
--- a/backend/src/views/home/index.vue
+++ b/backend/src/views/home/index.vue
@@ -5,7 +5,7 @@
diff --git a/backend/src/views/settings/systeminfo/index.vue b/backend/src/views/settings/systeminfo/index.vue
index e1de2cf..48d031e 100644
--- a/backend/src/views/settings/systeminfo/index.vue
+++ b/backend/src/views/settings/systeminfo/index.vue
@@ -12,7 +12,7 @@
- {{ systemInfo.name || '云泽管理系统' }}
+ {{ systemInfo.name || '温新云平台' }}
{{ systemInfo.version || '1.0.0' }}
@@ -227,7 +227,7 @@ async function fetchSystemInfo() {
// 模拟数据(如果有API会替换)
systemInfo.value = {
- name: '云泽管理系统',
+ name: '温新云平台',
version: '1.0.0',
environment: 'Production',
uptime: '24天 5小时 30分钟',
diff --git a/go/controllers/app_aggregate.go b/go/controllers/app_aggregate.go
index d54030a..d04ed81 100644
--- a/go/controllers/app_aggregate.go
+++ b/go/controllers/app_aggregate.go
@@ -97,6 +97,16 @@ func (c *AppAggregateController) Home() {
return
}
tid := uint64(claims.TenantId)
+ // 注册即登录:新用户尚未加入企业时令牌 tid=0。
+ // 此时按「原免登录功能」返回公开模块与公开菜单,其余模块由前端引导创建/挂靠企业。
+ if tid == 0 {
+ c.serveJSON(map[string]interface{}{
+ "code": 200,
+ "msg": "success",
+ "data": c.noTenantHome(claims),
+ })
+ return
+ }
uid := services.ResolveTenantUID(tid, uint64(claims.UserID))
modules := c.appModules(tid)
@@ -125,6 +135,77 @@ func (c *AppAggregateController) Home() {
})
}
+// noTenantHome tid=0(已注册、尚未加入企业)时的首屏数据。
+// 模块沿用公开宫格(保留 require_login 作为「需企业」标记),菜单只下发免登录菜单,
+// 因此新注册用户能直接使用原「免登录」功能,进入需企业模块时由端上引导申请。
+func (c *AppAggregateController) noTenantHome(claims *jwtutil.Claims) map[string]interface{} {
+ var modRows []models.SystemModules
+ _, _ = models.Orm.QueryTable(new(models.SystemModules)).
+ Filter("delete_time__isnull", true).
+ Filter("status", 1).
+ Filter("is_show", 1).
+ OrderBy("sort", "id").
+ All(&modRows)
+ modRows = services.FilterModulesByPlatform(modRows, models.PlatformApp)
+ modules := make([]publicModuleItem, 0, len(modRows))
+ for _, m := range modRows {
+ modules = append(modules, publicModuleItem{
+ ID: m.ID, Code: m.Code, Name: m.Name, Icon: m.Icon,
+ Path: m.Path, Description: m.Description, RequireLogin: m.RequireLogin,
+ })
+ }
+
+ var menuRows []models.SystemMenu
+ _, _ = models.Orm.QueryTable(new(models.SystemMenu)).
+ Filter("status", 1).
+ Filter("require_login", 0).
+ All(&menuRows)
+ allModules := c.allModules()
+ menus := make([]appMenuItem, 0, len(menuRows))
+ for _, m := range menuRows {
+ if m.Type == 3 {
+ continue
+ }
+ item := appMenuItem{
+ ID: m.ID, Pid: m.Pid, Type: m.Type, Sort: m.Sort,
+ Title: strings.TrimSpace(m.Title), Leaf: true,
+ }
+ if m.Path != nil {
+ item.Path = strings.TrimSpace(*m.Path)
+ }
+ if m.Icon != nil {
+ item.Icon = strings.TrimSpace(*m.Icon)
+ }
+ item.Module = appModuleCodeOfPath(item.Path, allModules)
+ menus = append(menus, item)
+ }
+
+ state := "none"
+ var apply models.AuthTenantApply
+ if err := models.Orm.QueryTable(new(models.AuthTenantApply)).
+ Filter("identity_id", uint64(claims.UserID)).
+ Filter("delete_time__isnull", true).
+ OrderBy("-id").One(&apply); err == nil {
+ switch apply.Status {
+ case models.TenantApplyStatusPending:
+ state = "pending"
+ case models.TenantApplyStatusReject:
+ state = "rejected"
+ }
+ }
+
+ return map[string]interface{}{
+ "user": map[string]interface{}{
+ "account": claims.Username, "tenant": "", "tid": 0, "need_tenant": true,
+ },
+ "modules": modules,
+ "menus": menus,
+ "stats": map[string]interface{}{},
+ "quick_menu_ids": []uint64{},
+ "tenant_state": state,
+ }
+}
+
// publicModuleItem 移动端免登录可见的模块卡片:只暴露展示与安全开关,不含菜单/KPI。
// 供未登录用户渲染 nav 宫格,require_login 决定点击是「进登录引导」还是「直接进入」。
type publicModuleItem struct {
diff --git a/go/controllers/auth/app_ticket.go b/go/controllers/auth/app_ticket.go
index 7e97a2d..4f3c623 100644
--- a/go/controllers/auth/app_ticket.go
+++ b/go/controllers/auth/app_ticket.go
@@ -111,7 +111,7 @@ func (c *AuthAppTicketController) Exchange() {
}
var req struct {
Ticket string `json:"ticket"`
- Token string `json:"token"` // 兼容 mp-login 的命名
+ Token string `json:"token"` // 兼容 mp-login 的命名
ClientID string `json:"client_id"`
}
if err := json.Unmarshal(body, &req); err != nil {
@@ -143,7 +143,7 @@ func (c *AuthAppTicketController) Exchange() {
return
}
- username := "云泽用户"
+ username := "温新云用户"
if identity.Nickname != nil && strings.TrimSpace(*identity.Nickname) != "" {
username = strings.TrimSpace(*identity.Nickname)
}
diff --git a/go/controllers/auth/wechat_miniapp.go b/go/controllers/auth/wechat_miniapp.go
index 4dc59c8..c265c82 100644
--- a/go/controllers/auth/wechat_miniapp.go
+++ b/go/controllers/auth/wechat_miniapp.go
@@ -17,7 +17,7 @@ import (
// 小程序登录失败原因(前端按此分支给出不同引导)
const (
MiniReasonNotConfigured = "not_configured" // 服务端未配置小程序
- MiniReasonIdentityNotFound = "identity_not_found" // 微信未匹配到云泽账号
+ MiniReasonIdentityNotFound = "identity_not_found" // 微信未匹配到温新云账号
MiniReasonNoTenant = "no_tenant" // 账号未加入任何企业
MiniReasonWechatAPI = "wechat_api_error" // 微信接口报错
)
@@ -120,7 +120,7 @@ func (c *AuthWechatMiniController) Login() {
return
}
- // 定位云泽身份:手机号优先(已通过微信验证),其次查已绑定的小程序 openid
+ // 定位温新云身份:手机号优先(已通过微信验证),其次查已绑定的小程序 openid
var mobile string
if req.PhoneCode != "" {
phone, perr := wechatmini.GetPhoneNumber(cfg, req.PhoneCode)
@@ -137,7 +137,7 @@ func (c *AuthWechatMiniController) Login() {
identity, err := c.resolveIdentity(cfg, mobile, session)
if err != nil {
- c.fail(MiniReasonIdentityNotFound, 404, "该微信未绑定云泽账号,请使用手机号一键登录或先注册")
+ c.fail(MiniReasonIdentityNotFound, 404, "该微信未绑定温新云账号,请使用手机号一键登录或先注册")
return
}
if identity.Status != models.AuthIdentityStatusEnabled {
@@ -147,15 +147,18 @@ func (c *AuthWechatMiniController) Login() {
// 企业选择:单企业直接进入,多企业签发待选令牌由前端展示选择界面
tenants, lerr := authsvc.ListTenantOptions(identity.ID)
- if lerr != nil || len(tenants) == 0 {
- c.fail(MiniReasonNoTenant, 403, "该账号未加入任何企业,请联系管理员")
+ if lerr != nil {
+ c.fail("", 400, "获取企业列表失败,请重试")
return
}
+ // 新流程:账号已注册但尚未加入企业时仍签发正式令牌(tid=0),
+ // 端上可继续使用原免登录模块,并在进入需企业模块时引导创建/挂靠企业。
+ noTenant := len(tenants) == 0
tid := uint64(0)
needChoose := false
if len(tenants) == 1 {
tid = tenants[0].Tid
- } else {
+ } else if len(tenants) > 1 {
tid = authsvc.PendingTenantID
needChoose = true
}
@@ -174,11 +177,12 @@ func (c *AuthWechatMiniController) Login() {
}
resp := map[string]interface{}{
- "code": 200,
- "msg": "登录成功",
- "tokens": tokens,
- "tenants": tenants,
- "identity": map[string]interface{}{"id": identity.ID, "nickname": username, "mobile": mobileOr(identity)},
+ "code": 200,
+ "msg": "登录成功",
+ "tokens": tokens,
+ "tenants": tenants,
+ "identity": map[string]interface{}{"id": identity.ID, "nickname": username, "mobile": mobileOr(identity)},
+ "need_tenant": noTenant,
}
if needChoose {
resp["need_choose_tenant"] = true
diff --git a/go/controllers/guest.go b/go/controllers/guest.go
index 3121017..87428be 100644
--- a/go/controllers/guest.go
+++ b/go/controllers/guest.go
@@ -114,7 +114,7 @@ func (c *GuestController) MiniLogin() {
return
}
- // 手机号已注册云泽账号(或该微信已绑定正式身份):直接签发正式令牌按登录处理。
+ // 手机号已注册温新云账号(或该微信已绑定正式身份):直接签发正式令牌按登录处理。
// (与 /auth/wechat/miniapp/login 同一套识别与签发逻辑)
if c.tryUserLogin(mobile, session) {
return
diff --git a/go/controllers/index_portal.go b/go/controllers/index_portal.go
index ef5175e..d14b8ff 100644
--- a/go/controllers/index_portal.go
+++ b/go/controllers/index_portal.go
@@ -87,11 +87,11 @@ func (c *IndexPortalController) GetHeadMenu() {
// 使用 OR 逻辑查询 name 或 short_name
cond := orm.NewCondition()
cond = cond.Or("tenant_name", tenantName).Or("tenant_short_name", tenantName)
-
+
err := models.Orm.QueryTable(new(models.SystemTenant)).
SetCond(cond).
One(&tenant)
-
+
if err != nil {
fmt.Printf("[DEBUG] QueryTenant Error: %v, Looking for: %s\n", err, tenantName)
} else {
@@ -206,9 +206,9 @@ func (c *IndexPortalController) GetFooterData() {
// 准备默认的 Footer 配置数据
footerMap := map[string]string{
- "sitename": "云泽官方网站",
- "companyname": "云泽智能科技有限公司",
- "copyright": "Copyright © 2026 Yunzer. All Rights Reserved.",
+ "sitename": "温新云官方网站",
+ "companyname": "连云港温新智能科技有限公司",
+ "copyright": "Copyright © 2026 WENXIN. All Rights Reserved.",
"icp": "苏ICP备2026000000号-1",
"description": "专业为企业建设数字化管理与官网门户的一站式 SaaS 服务平台",
"logo": "",
@@ -485,4 +485,4 @@ func (c *IndexPortalController) GetOnePageByPath() {
"data": nil,
}
_ = c.ServeJSON()
-}
\ No newline at end of file
+}
diff --git a/go/controllers/onboarding.go b/go/controllers/onboarding.go
new file mode 100644
index 0000000..5962e51
--- /dev/null
+++ b/go/controllers/onboarding.go
@@ -0,0 +1,471 @@
+package controllers
+
+// ==================== 小程序新用户引导 ====================
+//
+// 新的工作台口径:不再区分「登录 / 免登录」。
+// 1. 点任意模块卡片 → 微信手机号授权;
+// 2. 手机号已存在 → 前端走 /auth/wechat/miniapp/login 静默登录(自动绑定 openid);
+// 不存在 → 本文件 Phone 签发注册票据,Register 收 ≥6 位密码后建号并绑定微信,
+// 同时签发 tid=0 的正式令牌(注册即登录,可直接使用原免登录模块);
+// 3. 进入需企业模块前,Status 查企业绑定 / 申请状态;未通过则走创建企业或挂靠企业;
+// 4. 两类申请复用 services 层已有的审核闭环(platform / backend 审批端无需改动)。
+//
+// 本组路由均为公开路径(不在 middleware.protectedPrefixes 内):
+// phone / register 由微信凭证与一次性注册票据自证,其余端点自行校验 Bearer 令牌。
+
+import (
+ "encoding/json"
+ "io"
+ "strconv"
+ "strings"
+ "time"
+
+ "server/models"
+ "server/pkg/identity"
+ "server/pkg/jwtutil"
+ "server/pkg/passwordutil"
+ "server/services"
+ authsvc "server/services/auth"
+ "server/services/wechatmini"
+
+ "github.com/beego/beego/v2/core/logs"
+ beego "github.com/beego/beego/v2/server/web"
+)
+
+const onboardingClientID = "yz-miniapp"
+
+type OnboardingController struct {
+ beego.Controller
+}
+
+func (c *OnboardingController) reply(status int, msg string, data interface{}) {
+ c.Ctx.Output.SetStatus(status)
+ c.Data["json"] = map[string]interface{}{"code": status, "msg": msg, "data": data}
+ _ = c.ServeJSON()
+}
+
+func (c *OnboardingController) ok(data interface{}) {
+ c.reply(200, "success", data)
+}
+
+func (c *OnboardingController) fail(status int, msg string) {
+ c.reply(status, msg, nil)
+}
+
+func (c *OnboardingController) readJSON(v interface{}) bool {
+ body := c.Ctx.Input.RequestBody
+ if len(body) == 0 {
+ body, _ = io.ReadAll(c.Ctx.Request.Body)
+ }
+ if err := json.Unmarshal(body, v); err != nil {
+ c.fail(400, "参数错误")
+ return false
+ }
+ return true
+}
+
+// claims 当前登录态;允许 tid=0(刚注册、尚未加入企业)的正式令牌。
+func (c *OnboardingController) claims() (*jwtutil.Claims, error) {
+ return identity.Claims(c.Ctx.Request.Header.Get("Authorization"), identity.EndApp)
+}
+
+func (c *OnboardingController) mustClaims() *jwtutil.Claims {
+ cl, err := c.claims()
+ if err != nil || cl == nil || cl.UserID <= 0 {
+ c.fail(401, "登录态已失效,请重新授权手机号")
+ return nil
+ }
+ return cl
+}
+
+// Phone POST /api/onboarding/phone
+// body: { code(wx.login), phone_code(getPhoneNumber), appid? }
+// 返回 exists / need_register;无账号时附带一次性 register_token(内含微信手机号与 openid)。
+func (c *OnboardingController) Phone() {
+ var req struct {
+ Code string `json:"code"`
+ PhoneCode string `json:"phone_code"`
+ AppID string `json:"appid"`
+ }
+ if !c.readJSON(&req) {
+ return
+ }
+ cfg, err := loadGuestMiniConfig(req.AppID)
+ if err != nil {
+ c.fail(400, "小程序未配置,无法获取手机号")
+ return
+ }
+ phone, perr := wechatmini.GetPhoneNumber(cfg, strings.TrimSpace(req.PhoneCode))
+ if perr != nil {
+ msg := perr.Error()
+ if hint, ok := wechatmini.ExplainError(perr); ok {
+ msg = hint
+ }
+ logs.Warn("[onboarding] 获取手机号失败: %v", perr)
+ c.fail(400, "手机号授权失败:"+msg)
+ return
+ }
+ mobile := strings.TrimSpace(phone.PurePhoneNumber)
+ if mobile == "" {
+ mobile = strings.TrimSpace(phone.PhoneNumber)
+ }
+ if mobile == "" {
+ c.fail(400, "未获取到手机号")
+ return
+ }
+
+ // 已注册:交给前端用现有小程序一键登录静默登录(自动补绑 openid)
+ if exist := identityByPhone(mobile); exist != nil {
+ c.ok(map[string]interface{}{
+ "exists": true,
+ "need_register": false,
+ "mobile_mask": maskPhone(mobile),
+ })
+ return
+ }
+
+ // 注册必须同时绑定微信号:拿不到 openid 时不签发注册票据,避免留下「无绑定」账号
+ session := guestMiniSession(cfg, req.Code)
+ if session == nil || strings.TrimSpace(session.OpenID) == "" {
+ c.fail(400, "微信身份获取失败,请重新进入小程序授权")
+ return
+ }
+ token := services.IssuePendingRegisterToken(services.RegisterPending{
+ Mobile: mobile,
+ OpenID: session.OpenID,
+ UnionID: session.UnionID,
+ Provider: models.IdPWechatMini,
+ })
+ if token == "" {
+ c.fail(500, "注册会话创建失败,请稍后重试")
+ return
+ }
+ c.ok(map[string]interface{}{
+ "exists": false,
+ "need_register": true,
+ "register_token": token,
+ "mobile_mask": maskPhone(mobile),
+ })
+}
+
+// Register POST /api/onboarding/register
+// body: { register_token, password, confirm_password }
+// 密码最少 6 位;复用 CompleteRegister 建号 + 绑定微信,随后签发 tid=0 正式令牌。
+func (c *OnboardingController) Register() {
+ var req struct {
+ Token string `json:"register_token"`
+ Password string `json:"password"`
+ Confirm string `json:"confirm_password"`
+ }
+ if !c.readJSON(&req) {
+ return
+ }
+ tk := strings.TrimSpace(req.Token)
+ if tk == "" {
+ c.fail(400, "注册会话已失效,请重新授权手机号")
+ return
+ }
+ pwd := strings.TrimSpace(req.Password)
+ if len(pwd) < 6 {
+ c.fail(400, "密码长度不能少于6位")
+ return
+ }
+ if pwd != strings.TrimSpace(req.Confirm) {
+ c.fail(400, "两次输入的密码不一致")
+ return
+ }
+ hashed, herr := passwordutil.Hash(pwd)
+ if herr != nil {
+ c.fail(500, "密码处理失败,请重试")
+ return
+ }
+
+ var session models.AuthRegisterSession
+ if err := models.Orm.QueryTable(new(models.AuthRegisterSession)).Filter("token", tk).One(&session); err != nil {
+ c.fail(400, "注册会话已失效,请重新授权手机号")
+ return
+ }
+ if time.Now().After(session.ExpireAt) {
+ c.fail(400, "注册会话已过期,请重新授权手机号")
+ return
+ }
+ num, uerr := models.Orm.QueryTable(new(models.AuthRegisterSession)).
+ Filter("token", tk).
+ Update(map[string]interface{}{
+ "password_hash": hashed,
+ "password_algo": passwordutil.AlgoOf(hashed),
+ "update_time": time.Now(),
+ })
+ if uerr != nil || num == 0 {
+ c.fail(400, "注册会话已失效,请重新授权手机号")
+ return
+ }
+
+ identityID, err := services.CompleteRegister(tk)
+ if err != nil {
+ c.fail(400, err.Error())
+ return
+ }
+ // 注册会话一次性使用:账号已建,后续申请凭 Bearer 令牌
+ _, _ = models.Orm.QueryTable(new(models.AuthRegisterSession)).Filter("token", tk).Delete()
+
+ tokens, terr := c.issueNoTenantTokens(identityID, session.Mobile)
+ if terr != nil {
+ logs.Error("[onboarding] 签发无企业令牌失败: %v", terr)
+ c.fail(500, "注册成功但登录态签发失败,请重新进入小程序")
+ return
+ }
+ c.ok(map[string]interface{}{
+ "tokens": tokens,
+ "need_tenant": true,
+ "mobile_mask": maskPhone(session.Mobile),
+ })
+}
+
+// issueNoTenantTokens 注册即登录:签发 tid=0、userType=app 的正式令牌。
+// /api/home 对 tid=0 返回公开模块与公开菜单,因此原「免登录」功能可直接使用。
+func (c *OnboardingController) issueNoTenantTokens(identityID uint64, mobile string) (*authsvc.TokenPair, error) {
+ sess, err := authsvc.CreateSession(authsvc.SessionInfo{
+ IdentityID: identityID,
+ Tid: 0,
+ ClientID: onboardingClientID,
+ IP: c.Ctx.Input.IP(),
+ UserAgent: c.Ctx.Request.UserAgent(),
+ LoginType: "wechat_phone",
+ Amr: "wechat_phone",
+ })
+ if err != nil {
+ return nil, err
+ }
+ return authsvc.IssueTokens(authsvc.TokenIssue{
+ IdentityID: identityID,
+ Tid: 0,
+ ClientID: onboardingClientID,
+ Sid: sess.Sid,
+ Username: maskPhone(mobile),
+ UserType: "app",
+ Amr: "wechat_phone",
+ })
+}
+
+// Status GET /api/onboarding/status
+// 返回企业绑定、最新申请状态、微信手机号(前端只读展示)。
+func (c *OnboardingController) Status() {
+ cl := c.mustClaims()
+ if cl == nil {
+ return
+ }
+ identityID := uint64(cl.UserID)
+
+ var ai models.AuthIdentity
+ mobile := ""
+ if err := models.Orm.QueryTable(new(models.AuthIdentity)).
+ Filter("id", identityID).Filter("delete_time__isnull", true).One(&ai); err == nil && ai.Mobile != nil {
+ mobile = *ai.Mobile
+ }
+
+ var binds []models.AuthTenantUser
+ _, _ = models.Orm.QueryTable(new(models.AuthTenantUser)).
+ Filter("identity_id", identityID).
+ Filter("status", 1).
+ All(&binds)
+ hasTenant := len(binds) > 0
+ tenantName := ""
+ tid := uint64(0)
+ if hasTenant {
+ tid = binds[0].Tid
+ var t models.SystemTenant
+ if err := models.Orm.QueryTable(new(models.SystemTenant)).Filter("id", tid).One(&t); err == nil {
+ tenantName = t.TenantName
+ }
+ }
+
+ var applyData interface{}
+ var apply models.AuthTenantApply
+ if err := models.Orm.QueryTable(new(models.AuthTenantApply)).
+ Filter("identity_id", identityID).
+ Filter("delete_time__isnull", true).
+ OrderBy("-id").One(&apply); err == nil {
+ applyData = map[string]interface{}{
+ "id": apply.ID,
+ "type": apply.Type,
+ "status": apply.Status,
+ "tenant_name": apply.TenantName,
+ "reject_reason": apply.RejectReason,
+ "department_name": apply.DepartmentName,
+ "position_name": apply.PositionName,
+ "applicant_name": apply.ApplicantName,
+ }
+ }
+
+ c.ok(map[string]interface{}{
+ "has_tenant": hasTenant,
+ "tid": tid,
+ "tenant_name": tenantName,
+ "mobile": mobile,
+ "mobile_mask": maskPhone(mobile),
+ "apply": applyData,
+ })
+}
+
+// Tenants GET /api/onboarding/tenants?keyword=xxx
+// 挂靠企业:按企业全称搜索启用中的企业(复用注册页的企业联想)。
+func (c *OnboardingController) Tenants() {
+ items, err := services.SearchTenantsForRegister(c.GetString("keyword"))
+ if err != nil {
+ c.fail(500, "企业搜索失败")
+ return
+ }
+ c.ok(map[string]interface{}{"list": items})
+}
+
+// Departments GET /api/onboarding/departments?tid=xxx
+// 公开下拉:按企业返回组织(部门)列表。挂靠时用户尚未加入该企业,故不走 /backend 鉴权接口。
+func (c *OnboardingController) Departments() {
+ tid := c.uintParam("tid")
+ if tid == 0 {
+ c.fail(400, "请先选择企业")
+ return
+ }
+ var rows []models.BackendOrganization
+ _, err := models.Orm.QueryTable(new(models.BackendOrganization)).
+ Filter("tid", tid).
+ Filter("status", 1).
+ Filter("delete_time__isnull", true).
+ OrderBy("sort", "id").
+ All(&rows)
+ if err != nil {
+ c.fail(500, "部门列表读取失败")
+ return
+ }
+ list := make([]map[string]interface{}, 0, len(rows))
+ for _, r := range rows {
+ list = append(list, map[string]interface{}{
+ "id": r.ID, "name": r.OrgName, "parent_id": r.ParentID, "is_company": r.IsCompany,
+ })
+ }
+ c.ok(map[string]interface{}{"list": list})
+}
+
+// Positions GET /api/onboarding/positions?tid=xxx&department_id=xxx
+func (c *OnboardingController) Positions() {
+ tid := c.uintParam("tid")
+ if tid == 0 {
+ c.fail(400, "请先选择企业")
+ return
+ }
+ qs := models.Orm.QueryTable(new(models.BackendPosition)).
+ Filter("tid", tid).
+ Filter("status", 1).
+ Filter("delete_time__isnull", true)
+ if deptID := c.uintParam("department_id"); deptID > 0 {
+ qs = qs.Filter("department_id", deptID)
+ }
+ var rows []models.BackendPosition
+ if _, err := qs.OrderBy("sort", "id").All(&rows); err != nil {
+ c.fail(500, "职位列表读取失败")
+ return
+ }
+ list := make([]map[string]interface{}, 0, len(rows))
+ for _, r := range rows {
+ list = append(list, map[string]interface{}{
+ "id": r.ID, "name": r.PositionName, "department_id": r.DepartmentID,
+ })
+ }
+ c.ok(map[string]interface{}{"list": list})
+}
+
+// ApplyJoin POST /api/onboarding/apply-join
+// body: { tid, applicant_name, department_id, department_name, position_id, position_name }
+// 手机号不接受前端传值:由后端从微信验证过的身份手机号写入 wechat_mobile。
+func (c *OnboardingController) ApplyJoin() {
+ cl := c.mustClaims()
+ if cl == nil {
+ return
+ }
+ if c.hasTenant(uint64(cl.UserID)) {
+ c.fail(400, "你已加入企业,无需重复申请")
+ return
+ }
+ var req struct {
+ Tid uint64 `json:"tid"`
+ ApplicantName string `json:"applicant_name"`
+ DepartmentID uint64 `json:"department_id"`
+ DepartmentName string `json:"department_name"`
+ PositionID uint64 `json:"position_id"`
+ PositionName string `json:"position_name"`
+ }
+ if !c.readJSON(&req) {
+ return
+ }
+ if req.Tid == 0 || strings.TrimSpace(req.ApplicantName) == "" || req.DepartmentID == 0 || req.PositionID == 0 {
+ c.fail(400, "请填写姓名并选择企业、部门、职位")
+ return
+ }
+ mobile := c.identityMobile(uint64(cl.UserID))
+ apply, err := services.CreateJoinApply(uint64(cl.UserID), req.Tid, services.JoinApplyDetail{
+ ApplicantName: req.ApplicantName,
+ DepartmentID: req.DepartmentID,
+ DepartmentName: req.DepartmentName,
+ PositionID: req.PositionID,
+ PositionName: req.PositionName,
+ WechatMobile: mobile,
+ })
+ if err != nil {
+ c.fail(400, err.Error())
+ return
+ }
+ // 微信手机号单独落库,避免被表单联系人字段覆盖
+ _, _ = models.Orm.QueryTable(new(models.AuthTenantApply)).
+ Filter("id", apply.ID).
+ Update(map[string]interface{}{"wechat_mobile": mobile})
+ c.ok(map[string]interface{}{"apply_id": apply.ID, "status": apply.Status})
+}
+
+// ApplyCreate POST /api/onboarding/apply-create
+// 沿用 PC 注册页的完整字段与校验(信用代码 18 位、营业执照、法人、联系人、电话、邮箱必填)。
+func (c *OnboardingController) ApplyCreate() {
+ cl := c.mustClaims()
+ if cl == nil {
+ return
+ }
+ if c.hasTenant(uint64(cl.UserID)) {
+ c.fail(400, "你已加入企业,无需重复申请")
+ return
+ }
+ var req services.TenantCreateApplyInput
+ if !c.readJSON(&req) {
+ return
+ }
+ apply, err := services.CreateTenantApply(uint64(cl.UserID), req)
+ if err != nil {
+ c.fail(400, err.Error())
+ return
+ }
+ c.ok(map[string]interface{}{"apply_id": apply.ID, "status": apply.Status})
+}
+
+func (c *OnboardingController) hasTenant(identityID uint64) bool {
+ cnt, _ := models.Orm.QueryTable(new(models.AuthTenantUser)).
+ Filter("identity_id", identityID).
+ Filter("status", 1).
+ Count()
+ return cnt > 0
+}
+
+func (c *OnboardingController) identityMobile(identityID uint64) string {
+ var ai models.AuthIdentity
+ if err := models.Orm.QueryTable(new(models.AuthIdentity)).
+ Filter("id", identityID).Filter("delete_time__isnull", true).One(&ai); err != nil {
+ return ""
+ }
+ if ai.Mobile == nil {
+ return ""
+ }
+ return *ai.Mobile
+}
+
+func (c *OnboardingController) uintParam(key string) uint64 {
+ v, _ := strconv.ParseUint(strings.TrimSpace(c.GetString(key)), 10, 64)
+ return v
+}
diff --git a/go/controllers/platform_website_setting.go b/go/controllers/platform_website_setting.go
index a65c005..9e209e7 100644
--- a/go/controllers/platform_website_setting.go
+++ b/go/controllers/platform_website_setting.go
@@ -33,7 +33,6 @@ func (c *PlatformWebsiteSettingController) jsonErr(httpStatus, bizCode int, msg
// Info GET /platform/website/setting/info
// 直接把表里所有设置项返回,前端按行渲染(新增设置项无需改代码):
// data.list = [{ id, title, value, tag, remark }]
-// data.values = { "sitename": "云泽科技", ... }(key 即标签名,方便直接使用)
func (c *PlatformWebsiteSettingController) Info() {
if _, err := requirePlatform(&c.Controller); err != nil {
code := identity.Status(err)
@@ -65,7 +64,6 @@ func (c *PlatformWebsiteSettingController) Info() {
}
// Save POST /platform/website/setting/save
-// 入参 { "sitename": "云泽科技", "subtitle": "...", ... },key 为标签名,按标签逐项 upsert。
// 通用:表里已存在或新增的任意合法标签都可写入,不需要登记。
func (c *PlatformWebsiteSettingController) Save() {
if _, err := requirePlatform(&c.Controller); err != nil {
diff --git a/go/docs/sql/alter_auth_tenant_apply_onboarding.sql b/go/docs/sql/alter_auth_tenant_apply_onboarding.sql
new file mode 100644
index 0000000..5e9a4f5
--- /dev/null
+++ b/go/docs/sql/alter_auth_tenant_apply_onboarding.sql
@@ -0,0 +1,14 @@
+-- ============================================================
+-- 小程序「挂靠企业」申请表补列
+-- 前置:yz_auth_tenant_apply 已由 create_auth_tenant_apply.sql 创建。
+-- 幂等说明:MySQL 5.7/8.0 不支持 ADD COLUMN IF NOT EXISTS,
+-- 重复执行会报 Duplicate column,可忽略(先 SELECT 确认)。
+-- ============================================================
+
+ALTER TABLE `yz_auth_tenant_apply`
+ ADD COLUMN `applicant_name` varchar(64) NOT NULL DEFAULT '' COMMENT '申请人姓名(挂靠表单填写)' AFTER `identity_id`,
+ ADD COLUMN `department_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT '部门ID(yz_backend_organization.id)' AFTER `applicant_name`,
+ ADD COLUMN `department_name` varchar(128) NOT NULL DEFAULT '' COMMENT '部门名称快照' AFTER `department_id`,
+ ADD COLUMN `position_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT '职位ID(yz_backend_position.id)' AFTER `department_name`,
+ ADD COLUMN `position_name` varchar(128) NOT NULL DEFAULT '' COMMENT '职位名称快照' AFTER `position_id`,
+ ADD COLUMN `wechat_mobile` varchar(20) NOT NULL DEFAULT '' COMMENT '微信验证手机号(只读,后端写入)' AFTER `position_name`;
diff --git a/go/docs/sql/create_platform_website_notice.sql b/go/docs/sql/create_platform_website_notice.sql
index b6c4f6d..f2cc4cd 100644
--- a/go/docs/sql/create_platform_website_notice.sql
+++ b/go/docs/sql/create_platform_website_notice.sql
@@ -84,5 +84,5 @@ WHERE @notice_pid IS NOT NULL
INSERT INTO `yz_platform_website_notice`
(`title`, `summary`, `content`, `type`, `is_popup`, `top`, `sort`, `status`, `publish_time`)
VALUES
- ('云泽科技官网全新改版上线', '本次改版聚焦信息架构与访问体验,欢迎反馈建议。',
- '云泽科技官网全新改版上线,本次改版聚焦信息架构与访问体验。
', 1, 1, 1, 0, 1, NOW());
+ ('温新云科技官网全新改版上线', '本次改版聚焦信息架构与访问体验,欢迎反馈建议。',
+ '温新云科技官网全新改版上线,本次改版聚焦信息架构与访问体验。
', 1, 1, 1, 0, 1, NOW());
diff --git a/go/migrate_auth_conflict_20260919004551.csv b/go/migrate_auth_conflict_20260919004551.csv
deleted file mode 100644
index 0e4bc1c..0000000
--- a/go/migrate_auth_conflict_20260919004551.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-merge_key,row_count,password_variants,tids,representative_id
-m:19895983967,2,2,1|2,4
diff --git a/go/migrate_auth_conflict_20260919004725.csv b/go/migrate_auth_conflict_20260919004725.csv
deleted file mode 100644
index 0e4bc1c..0000000
--- a/go/migrate_auth_conflict_20260919004725.csv
+++ /dev/null
@@ -1,2 +0,0 @@
-merge_key,row_count,password_variants,tids,representative_id
-m:19895983967,2,2,1|2,4
diff --git a/go/migrate_auth_map_20260919004727.csv b/go/migrate_auth_map_20260919004727.csv
deleted file mode 100644
index 9e1fd56..0000000
--- a/go/migrate_auth_map_20260919004727.csv
+++ /dev/null
@@ -1,4 +0,0 @@
-old_id,old_uid,tid,identity_id,tenant_user_id,account,merge_key,conflict
-2,96085169,2,1,1,test1,m:13357867407,false
-1,88888888,1,2,2,hero920103,m:19895983967,true
-4,67091493,2,2,3,hero920103,m:19895983967,true
diff --git a/go/models/auth_tenant_apply.go b/go/models/auth_tenant_apply.go
index 9525169..8dcd459 100644
--- a/go/models/auth_tenant_apply.go
+++ b/go/models/auth_tenant_apply.go
@@ -22,6 +22,13 @@ const (
type AuthTenantApply struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
IdentityID uint64 `orm:"column(identity_id)" json:"identity_id"`
+ // 小程序「挂靠企业」填写的资料:姓名 / 部门 / 职位 / 微信手机号(只读,后端取身份手机号)
+ ApplicantName string `orm:"column(applicant_name);size(64);default('')" json:"applicant_name"`
+ DepartmentID uint64 `orm:"column(department_id);default(0)" json:"department_id"`
+ DepartmentName string `orm:"column(department_name);size(128);default('')" json:"department_name"`
+ PositionID uint64 `orm:"column(position_id);default(0)" json:"position_id"`
+ PositionName string `orm:"column(position_name);size(128);default('')" json:"position_name"`
+ WechatMobile string `orm:"column(wechat_mobile);size(20);default('')" json:"wechat_mobile"`
Type int8 `orm:"column(type);default(1)" json:"type"`
Tid uint64 `orm:"column(tid);default(0)" json:"tid"`
TenantName string `orm:"column(tenant_name);size(128);default('')" json:"tenant_name"`
diff --git a/go/output.log b/go/output.log
deleted file mode 100644
index b475a1f..0000000
Binary files a/go/output.log and /dev/null differ
diff --git a/go/routers/api/api.go b/go/routers/api/api.go
index 35d9cc9..c165da9 100644
--- a/go/routers/api/api.go
+++ b/go/routers/api/api.go
@@ -185,4 +185,16 @@ func Register() {
beego.Router("/api/guest/data/:id", &controllers.GuestController{}, "put:DataUpdate;delete:DataDelete")
// 移动端用户自定义「常用功能」保存(读取随 /api/home 下发 quick_menu_ids)
beego.Router("/api/quick-menus", &controllers.AppAggregateController{}, "post:SaveQuickMenus")
+
+ // ===== 小程序新用户引导:手机号 → 设密码注册 → 创建 / 挂靠企业 =====
+ // 公开路径(不在 jwt 保护前缀内):phone / register 由微信凭证与注册票据自证,
+ // 其余端点由控制器自行校验 Bearer 令牌(含 tid=0 的未加入企业令牌)。
+ beego.Router("/api/onboarding/phone", &controllers.OnboardingController{}, "post:Phone")
+ beego.Router("/api/onboarding/register", &controllers.OnboardingController{}, "post:Register")
+ beego.Router("/api/onboarding/status", &controllers.OnboardingController{}, "get:Status")
+ beego.Router("/api/onboarding/tenants", &controllers.OnboardingController{}, "get:Tenants")
+ beego.Router("/api/onboarding/departments", &controllers.OnboardingController{}, "get:Departments")
+ beego.Router("/api/onboarding/positions", &controllers.OnboardingController{}, "get:Positions")
+ beego.Router("/api/onboarding/apply-join", &controllers.OnboardingController{}, "post:ApplyJoin")
+ beego.Router("/api/onboarding/apply-create", &controllers.OnboardingController{}, "post:ApplyCreate")
}
diff --git a/go/services/identity_register.go b/go/services/identity_register.go
index e99cc4b..abc8cd7 100644
--- a/go/services/identity_register.go
+++ b/go/services/identity_register.go
@@ -436,8 +436,20 @@ func SearchTenantsForRegister(keyword string) ([]TenantSearchItem, error) {
return out, nil
}
+// JoinApplyDetail 小程序「挂靠企业」补充资料。
+// 手机号不允许前端传:统一由后端从身份表读取微信验证过的手机号。
+type JoinApplyDetail struct {
+ ApplicantName string
+ DepartmentID uint64
+ DepartmentName string
+ PositionID uint64
+ PositionName string
+ WechatMobile string
+}
+
// CreateJoinApply 「绑定企业」申请:等待目标企业管理员审批
-func CreateJoinApply(identityID, tid uint64) (*models.AuthTenantApply, error) {
+// detail 为可选参数,PC 两步式注册沿用原调用(不传),小程序挂靠表单传入补充资料。
+func CreateJoinApply(identityID, tid uint64, detail ...JoinApplyDetail) (*models.AuthTenantApply, error) {
if identityID == 0 || tid == 0 {
return nil, errors.New("参数错误")
}
@@ -477,7 +489,22 @@ func CreateJoinApply(identityID, tid uint64) (*models.AuthTenantApply, error) {
TenantName: tenant.TenantName,
Status: models.TenantApplyStatusPending,
}
- apply.ContactPerson = identityNickname(identity)
+ // 小程序挂靠表单:补充姓名 / 部门 / 职位,手机号取微信验证过的身份手机号
+ if len(detail) > 0 {
+ d := detail[0]
+ apply.ApplicantName = strings.TrimSpace(d.ApplicantName)
+ apply.DepartmentID = d.DepartmentID
+ apply.DepartmentName = strings.TrimSpace(d.DepartmentName)
+ apply.PositionID = d.PositionID
+ apply.PositionName = strings.TrimSpace(d.PositionName)
+ apply.WechatMobile = strings.TrimSpace(d.WechatMobile)
+ if apply.ApplicantName != "" {
+ apply.ContactPerson = apply.ApplicantName
+ }
+ }
+ if apply.ContactPerson == "" {
+ apply.ContactPerson = identityNickname(identity)
+ }
if identity.Mobile != nil {
apply.ContactPhone = *identity.Mobile
}
@@ -622,6 +649,13 @@ func ApproveJoinApply(applyID, tid, reviewerID uint64, reviewerName string) erro
if err := bindIdentityToTenant(identity, tid, models.GetTenantUserRole); err != nil {
return err
}
+ // 挂靠申请选了部门:审批通过后把 org_id 落到认证中心绑定行
+ if apply.DepartmentID > 0 {
+ _, _ = models.Orm.QueryTable(new(models.AuthTenantUser)).
+ Filter("tid", tid).
+ Filter("identity_id", apply.IdentityID).
+ Update(map[string]interface{}{"org_id": apply.DepartmentID, "update_time": time.Now()})
+ }
return finishApply(apply, models.TenantApplyStatusPassed, "", reviewerID, reviewerName)
}
diff --git a/go/services/reminder_scheduler.go b/go/services/reminder_scheduler.go
index 1ada761..ec21391 100644
--- a/go/services/reminder_scheduler.go
+++ b/go/services/reminder_scheduler.go
@@ -343,7 +343,7 @@ func (s *WechatMpSender) Send(ctx context.Context, reminder ReminderData, title,
perr := wechatmp.PushToOpenID(cfg, openid, wechatmp.PushMessage{
Title: title,
Content: content,
- Remark: "来自云泽管理平台日程提醒",
+ Remark: "来自温新云平台日程提醒",
})
if perr != nil {
return false, perr
diff --git a/go/services/wechatmini/api.go b/go/services/wechatmini/api.go
index 51330db..c21547b 100644
--- a/go/services/wechatmini/api.go
+++ b/go/services/wechatmini/api.go
@@ -46,6 +46,8 @@ func (e *APIError) Explain() string {
return "AppID 无效:请核对小程序后台「开发 → 开发管理」中的 AppID"
case 40125:
return "AppSecret 错误:请在小程序后台重置后复制新的 AppSecret 填入本页"
+ case 40164, 61004:
+ return "服务器出口 IP 不在白名单:请在小程序后台「开发管理 → 开发设置 → IP白名单」加入本服务器出口 IP"
case 40226:
return "该小程序未开通手机号快速验证能力:请在「开发管理 → 接口设置」中开通"
case 45009:
diff --git a/go/test-output-current.txt b/go/test-output-current.txt
deleted file mode 100644
index c281cbd..0000000
Binary files a/go/test-output-current.txt and /dev/null differ
diff --git a/go/test-output-tenant-login.txt b/go/test-output-tenant-login.txt
deleted file mode 100644
index c281cbd..0000000
Binary files a/go/test-output-tenant-login.txt and /dev/null differ
diff --git a/go/views/auth/forgot.tpl b/go/views/auth/forgot.tpl
index b05367d..6f16643 100644
--- a/go/views/auth/forgot.tpl
+++ b/go/views/auth/forgot.tpl
@@ -187,7 +187,7 @@
- 云泽统一认证中心
+ 温新云统一认证中心
找回登录密码
diff --git a/go/views/auth/login.tpl b/go/views/auth/login.tpl
index d9dd6a7..ecd7d11 100644
--- a/go/views/auth/login.tpl
+++ b/go/views/auth/login.tpl
@@ -560,7 +560,7 @@
- 云泽统一认证中心
+ 温新云统一认证中心
{{end}}
@@ -592,7 +592,7 @@
{{end}}
- 云泽统一认证中心
+ 温新云统一认证中心
{{.LoginTitle}}
@@ -769,7 +769,7 @@
微信扫码登录
- 使用微信「扫一扫」,扫码后在云泽小程序中点击确认
即可完成登录
+ 使用微信「扫一扫」,扫码后在温新云小程序中点击确认
即可完成登录
二维码 5 分钟内有效,过期可点击刷新
@@ -799,7 +799,7 @@
diff --git a/platform/src/views/settings/systeminfo/index.vue b/platform/src/views/settings/systeminfo/index.vue
index f970468..977e495 100644
--- a/platform/src/views/settings/systeminfo/index.vue
+++ b/platform/src/views/settings/systeminfo/index.vue
@@ -12,7 +12,7 @@
- {{ systemInfo.name || '云泽管理系统' }}
+ {{ systemInfo.name || '温新云平台' }}
{{ systemInfo.version || '1.0.0' }}
@@ -226,7 +226,7 @@ async function fetchSystemInfo() {
// 模拟数据(如果有API会替换)
systemInfo.value = {
- name: '云泽管理系统',
+ name: '温新云平台',
version: '1.0.0',
environment: 'Production',
uptime: '24天 5小时 30分钟',
diff --git a/platform/src/views/system/aisettings/playground.vue b/platform/src/views/system/aisettings/playground.vue
index 9047b15..5e80be3 100644
--- a/platform/src/views/system/aisettings/playground.vue
+++ b/platform/src/views/system/aisettings/playground.vue
@@ -216,7 +216,7 @@ const totalTokens = computed(() =>
const scenes = [
{ label: "通用问答", prompt: "用三句话介绍一下你自己" },
{ label: "代码生成", prompt: "用 Vue3 写一个防抖函数示例" },
- { label: "文案翻译", prompt: "把「欢迎使用云泽科技」翻译成英文和日文" },
+ { label: "文案翻译", prompt: "把「欢迎使用温新云科技」翻译成英文和日文" },
{ label: "OCR 提示词", prompt: "请把图片中的文字完整识别出来,保持原有排版" },
];
diff --git a/platform/src/views/system/platformsettings/components/loginBrandSettings.vue b/platform/src/views/system/platformsettings/components/loginBrandSettings.vue
index 061d953..96536d9 100644
--- a/platform/src/views/system/platformsettings/components/loginBrandSettings.vue
+++ b/platform/src/views/system/platformsettings/components/loginBrandSettings.vue
@@ -98,7 +98,7 @@
class="preview-logo"
alt="Logo"
/>
- 云泽统一认证中心
+ 温新云统一认证中心
{{ form.login_title || "一次登录,全平台通行" }}
{{ form.login_subtitle || "基于 OIDC / OAuth 2.0 的统一身份底座" }}
diff --git a/platform/src/views/system/platformsettings/components/loginSettings.vue b/platform/src/views/system/platformsettings/components/loginSettings.vue
index 83d392c..74ee9f1 100644
--- a/platform/src/views/system/platformsettings/components/loginSettings.vue
+++ b/platform/src/views/system/platformsettings/components/loginSettings.vue
@@ -23,7 +23,7 @@
- 微信扫码登录(云泽小程序内确认)。关闭后登录页不再显示该 tab。
+ 微信扫码登录(温新云小程序内确认)。关闭后登录页不再显示该 tab。
-
+
diff --git a/platform/src/views/tenantApply/index.vue b/platform/src/views/tenantApply/index.vue
index 7640a4c..8ccd2c8 100644
--- a/platform/src/views/tenantApply/index.vue
+++ b/platform/src/views/tenantApply/index.vue
@@ -68,6 +68,15 @@
{{ scope.row.identity_mobile || "" }}
+
+
+ {{ scope.row.applicant_name || "-" }}
+
+ {{ scope.row.department_name || "-" }} · {{ scope.row.position_name || "-" }}
+
+ 微信手机号 {{ scope.row.wechat_mobile || "-" }}
+
+
{{ avatarText }}
- {{ user.name || '云泽用户' }}
- {{ user.tenant || '云泽平台' }} · {{ user.account }}
+ {{ user.name || '温新云用户' }}
+ {{ user.tenant || '温新云平台' }} · {{ user.account }}
@@ -32,8 +32,8 @@
{{ avatarText }}
- {{ user.name || '云泽用户' }}
- {{ user.tenant || '云泽平台' }} · {{ user.account }}
+ {{ user.name || '温新云用户' }}
+ {{ user.tenant || '温新云平台' }} · {{ user.account }}
diff --git a/uniapp/manifest.json b/uniapp/manifest.json
index 7f52e44..d09e241 100644
--- a/uniapp/manifest.json
+++ b/uniapp/manifest.json
@@ -81,7 +81,7 @@
},
"vueVersion" : "3",
"h5" : {
- "title" : "云泽系统",
+ "title" : "温新云科技",
"router" : {
"mode" : "hash",
"base" : "/h5/"
diff --git a/uniapp/pages.json b/uniapp/pages.json
index f82c6c3..a96137a 100644
--- a/uniapp/pages.json
+++ b/uniapp/pages.json
@@ -50,6 +50,20 @@
"navigationBarTitleText": "确认注册"
}
},
+ {
+ "path": "pages/onboarding/register",
+ "style": {
+ "navigationStyle": "custom",
+ "navigationBarTitleText": "设置密码"
+ }
+ },
+ {
+ "path": "pages/onboarding/company",
+ "style": {
+ "navigationStyle": "custom",
+ "navigationBarTitleText": "加入企业"
+ }
+ },
{
"path": "pages/login/geetest-webview",
"style": {
@@ -621,7 +635,7 @@
],
"globalStyle": {
"navigationBarTextStyle": "black",
- "navigationBarTitleText": "云泽",
+ "navigationBarTitleText": "温新云",
"navigationBarBackgroundColor": "#F8FAFC",
"backgroundColor": "#F8FAFC",
"rpxCalcMaxDeviceWidth": 560,
diff --git a/uniapp/pages/dashboard/dashboard.vue b/uniapp/pages/dashboard/dashboard.vue
index 44014a4..598366d 100644
--- a/uniapp/pages/dashboard/dashboard.vue
+++ b/uniapp/pages/dashboard/dashboard.vue
@@ -221,7 +221,7 @@ const orgLine = computed(() => {
if (user.value.tenant) parts.push(user.value.tenant)
if (user.value.account) parts.push(user.value.account)
if (user.value.role_name) parts.push(user.value.role_name)
- return parts.length ? parts.join(' · ') : '云泽统一账号'
+ return parts.length ? parts.join(' · ') : '温新云统一账号'
})
const badgeText = computed(() => (stats.value.message > 99 ? '99+' : String(stats.value.message || '')))
diff --git a/uniapp/pages/login/login.vue b/uniapp/pages/login/login.vue
index b8a7614..14b31f9 100644
--- a/uniapp/pages/login/login.vue
+++ b/uniapp/pages/login/login.vue
@@ -20,7 +20,7 @@
- 云泽统一认证
+ 温新云统一认证
一次登录,全平台通行
diff --git a/uniapp/pages/nav/nav.vue b/uniapp/pages/nav/nav.vue
index 8937650..f9eaff9 100644
--- a/uniapp/pages/nav/nav.vue
+++ b/uniapp/pages/nav/nav.vue
@@ -4,18 +4,18 @@
- YUNZE WORKSPACE
+ WENXIN WORKSPACE
工作台
{{ user.account || '我的账号' }}
- 登录
+ 游客
- 当前租户{{ user.tenant || '云泽平台' }}
+ 当前企业{{ user.tenant || '尚未加入企业' }}
切换
- {{ guest ? '免登录可逛部分功能,登录后使用完整工作台' : '选择一个业务系统,开始高效工作' }}
+
@@ -36,8 +36,28 @@
+
+
+
+
+
{{ card.name }}
- {{ card.needLogin ? '需登录' : '免登录' }}
@@ -58,11 +77,58 @@
-
+
+
+
+
+
+
+
+
+
+ {{ tenantPickerMode === 'login' ? '选择要进入的企业' : '切换企业' }}
+ {{ tenantPickerMode === 'login' ? '该账号已加入多家企业,请选择工作空间' : '选择一家企业,工作台将随之刷新' }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.tenant_name || '企业' + item.tid }}
+ {{ item.name ? item.name + ' · ' : '' }}企业 ID {{ item.tid }}
+
+ 当前
+ 进入中…
+
+
+
+
+
+
+
@@ -73,8 +139,10 @@ import { mpHeadPad } from '@/utils/mpHead.js'
const headPad = mpHeadPad()
import { onShow } from '@dcloudio/uni-app'
import { isLoggedIn, getTenants, getTid, setAuthTokens, setTenants, setTid, setTenantName, getUser, setUser } from '@/utils/auth.js'
-import { switchTenant } from '@/api/auth.js'
-import { fetchHome, fetchPublicModules, fetchPublicMenus } from '@/api/workspace.js'
+import { switchTenant, loginByMiniapp } from '@/api/auth.js'
+import { fetchHome, fetchPublicModules, fetchPublicMenus, clearHomeCache } from '@/api/workspace.js'
+import { onboardingPhone, getOnboardingStatus } from '@/api/onboarding.js'
+import { setPendingCard, takePendingCard } from '@/utils/onboarding.js'
import { openMenu } from '@/utils/nav.js'
import { getSubsystemByModule, goSubsystem } from '@/config/subsystems.js'
import { getGuestToken, setGuestToken, clearGuestToken } from '@/utils/guest.js'
@@ -86,6 +154,12 @@ const guestAuthVisible = ref(false)
const guestAuthLoading = ref(false)
// 本次停留内跳过:关闭后不在 nav 反复弹窗,下次冷启动再引导
const guestAuthDismissed = ref(false)
+const phoneBusy = ref(false)
+// 非微信端没有 getPhoneNumber 能力:卡片退化为普通点击并给出提示
+let isMp = false
+// #ifdef MP-WEIXIN
+isMp = true
+// #endif
/** 仅微信小程序可拿手机号动态凭证 */
function canRequestGuestPhone() {
@@ -170,36 +244,83 @@ async function completeUserLogin(res) {
uni.showToast({ title: '已登录,欢迎回来', icon: 'none' })
}
-/** 待选状态下必须选一个企业才能继续,关闭选择器后再次拉起 */
+/* ==================== 企业选择抽屉(替代 uni.showActionSheet) ==================== */
+// 原生 actionSheet 只有一行居中文本:长企业名挤成一片、无法标注「当前企业」。
+// 登录后的首次选择必须选一家(不可关闭),主动切换可取消,两者共用这一套抽屉。
+const tenantPickerVisible = ref(false)
+const tenantPickerRequired = ref(false)
+const tenantPickerMode = ref('login') // login=登录后的首次选择 / switch=主动切换
+const tenantPickerList = ref([])
+const tenantPickingTid = ref('')
+
+function openTenantPicker(list, mode, required) {
+ tenantPickerList.value = Array.isArray(list) ? list : []
+ tenantPickerMode.value = mode
+ tenantPickerRequired.value = required
+ tenantPickingTid.value = ''
+ tenantPickerVisible.value = true
+}
+
+function closeTenantPicker() {
+ if (tenantPickingTid.value) return
+ if (tenantPickerRequired.value) {
+ uni.showToast({ title: '请选择一家企业以继续', icon: 'none' })
+ return
+ }
+ tenantPickerVisible.value = false
+}
+
+function isCurrentTenant(item) {
+ return tenantPickerMode.value === 'switch' && item && String(item.tid) === String(getTid())
+}
+
+function isPickingAny() {
+ return tenantPickingTid.value !== ''
+}
+
+function isPickingTenant(item) {
+ return item && tenantPickingTid.value === String(item.tid)
+}
+
+/** 待选状态下必须选一家企业才能继续 */
function askChooseTenant(list) {
- uni.showActionSheet({
- itemList: list.map((t) => t.tenant_name || `企业${t.tid}`),
- success: async ({ tapIndex }) => {
- const tenant = list[tapIndex]
- if (!tenant) return
- uni.showLoading({ title: '登录中...', mask: true })
- try {
- const sw = await switchTenant({ tid: tenant.tid })
- if (!sw || !sw.tokens) throw new Error((sw && sw.msg) || '进入企业失败')
- setAuthTokens(sw.tokens)
- setTenants(list)
- setTid(tenant.tid)
- setTenantName(tenant.tenant_name || '')
- // 先游客后转正:选定企业后移植游客数据
- await mergeGuestIfAny()
- await load(true)
- uni.showToast({ title: '已登录,欢迎回来', icon: 'none' })
- } catch (e) {
- uni.showToast({ title: (e && e.message) || '进入失败,请重试', icon: 'none' })
- } finally {
- uni.hideLoading()
- }
- },
- fail: () => {
- uni.showToast({ title: '请选择要进入的企业', icon: 'none' })
- setTimeout(() => askChooseTenant(list), 800)
+ openTenantPicker(list, 'login', true)
+}
+
+async function pickTenant(item) {
+ if (!item || isPickingAny() || isCurrentTenant(item)) return
+ const isSwitch = tenantPickerMode.value === 'switch'
+ tenantPickingTid.value = String(item.tid)
+ switchingTenant.value = true
+ uni.showLoading({ title: isSwitch ? '正在切换...' : '登录中...', mask: true })
+ try {
+ const res = await switchTenant({ tid: item.tid })
+ if (!res || !res.tokens) throw new Error((res && res.msg) || (isSwitch ? '切换企业失败' : '进入企业失败'))
+ setAuthTokens(res.tokens)
+ setTenants(tenantPickerList.value)
+ setTid(item.tid)
+ setTenantName(item.tenant_name || '')
+ if (isSwitch) {
+ const profile = getUser() || {}
+ const nextUser = { ...profile, tid: item.tid, tenant_name: item.tenant_name || '' }
+ setUser(nextUser)
+ user.value = { ...user.value, tenant: item.tenant_name || '', account: nextUser.account || user.value.account }
}
- })
+ // 成功才关闭;失败保留抽屉让用户直接重试
+ tenantPickerRequired.value = false
+ tenantPickerVisible.value = false
+ // 先游客后转正:企业落定后把游客数据并入正式账号
+ await mergeGuestIfAny()
+ await load(true)
+ uni.showToast({ title: isSwitch ? '已切换企业' : '已登录,欢迎回来', icon: isSwitch ? 'success' : 'none' })
+ } catch (e) {
+ console.error('[nav] switch tenant failed:', e)
+ uni.showToast({ title: (e && e.message) || '进入失败,请重试', icon: 'none' })
+ } finally {
+ uni.hideLoading()
+ tenantPickingTid.value = ''
+ switchingTenant.value = false
+ }
}
function skipGuestAuth() {
@@ -262,17 +383,17 @@ const cards = computed(() => {
if (guest.value) {
return publicModules.value.map((mod, index) => {
const code = String(mod.code || '').toLowerCase()
- const needLogin = !guestMenuModuleSet.value.has(code)
+ const needTenant = mod.require_login !== 0
return {
id: mod.id,
code: mod.code,
path: mod.path || '',
name: mod.name || '未命名系统',
- desc: mod.description || (needLogin ? '需登录使用' : '免登录'),
+ desc: mod.description || (needTenant ? '加入企业后可用' : '可直接使用'),
icon: MODULE_ICONS[code] || 'cube',
tint: 'tint-' + (index % 8),
locked: false,
- needLogin,
+ needTenant,
items: [],
total: 0
}
@@ -303,6 +424,7 @@ const cards = computed(() => {
icon: MODULE_ICONS[code] || 'cube',
tint: locked ? 'tint-off' : 'tint-' + (index % 8),
locked,
+ needTenant: mod.require_login !== 0,
items,
total: items.length
}
@@ -329,8 +451,7 @@ async function load(force = false) {
} finally {
loading.value = false
}
- // 进小程序即引导授权:游客无令牌时弹手机号授权窗
- maybeAskGuestAuth()
+ // 新流程:不再自动弹授权,点任意模块卡片时才拉起微信手机号授权
return
}
try {
@@ -352,56 +473,123 @@ async function load(force = false) {
function chooseTenant() {
const list = tenants.value
- if (list.length <= 1) { uni.showToast({ title: list.length ? '当前账号仅绑定一个租户' : '暂无可切换的租户', icon: 'none' })
+ if (list.length <= 1) { uni.showToast({ title: list.length ? '当前账号仅绑定一个企业' : '暂无可切换的企业', icon: 'none' })
return
}
- if (switchingTenant.value) return
- uni.showActionSheet({
- itemList: list.map((tenant) => tenant.tenant_name || `企业${tenant.tid}`),
- success: async ({ tapIndex }) => {
- const tenant = list[tapIndex]
- if (!tenant || String(tenant.tid) === String(getTid())) return
- switchingTenant.value = true
- uni.showLoading({ title: '正在切换...', mask: true })
- try {
- const res = await switchTenant({ tid: tenant.tid })
- if (!res || !res.tokens) throw new Error((res && res.msg) || '切换租户失败')
- setAuthTokens(res.tokens)
- setTid(tenant.tid)
- setTenantName(tenant.tenant_name || '')
- const profile = getUser() || {}
- const nextUser = { ...profile, tid: tenant.tid, tenant_name: tenant.tenant_name || '' }
- setUser(nextUser)
- user.value = { ...user.value, tenant: tenant.tenant_name || '', account: nextUser.account || user.value.account }
- await load(true)
- uni.showToast({ title: '已切换租户', icon: 'success' })
- } catch (e) {
- uni.showToast({ title: (e && e.message) || '切换失败,请重试', icon: 'none' })
- } finally {
- uni.hideLoading()
- switchingTenant.value = false
- }
+ if (switchingTenant.value || tenantPickerVisible.value) return
+ openTenantPicker(list, 'switch', false)
+}
+
+/* ==================== 手机号授权 / 注册 / 企业分流 ==================== */
+
+/** 点卡片(未授权时)直接触发微信手机号授权;有账号静默登录,无账号去设密码注册 */
+async function onCardPhone(e, card) {
+ const detail = (e && e.detail) || {}
+ if (String(detail.errMsg || '').indexOf('ok') === -1 || !detail.code) {
+ uni.showToast({ title: '需要授权手机号才能使用', icon: 'none' })
+ return
+ }
+ if (phoneBusy.value) return
+ phoneBusy.value = true
+ uni.showLoading({ title: '验证中...', mask: true })
+ try {
+ const code = await navUniLoginCode()
+ let appid = ''
+ try {
+ appid = (uni.getAccountInfoSync().miniProgram || {}).appId || ''
+ } catch (err) {
+ appid = ''
}
- })
+ setPendingCard(card)
+ const res = await onboardingPhone({ code, phoneCode: detail.code, appid })
+ if (res && res.need_register) {
+ uni.hideLoading()
+ uni.navigateTo({
+ url: '/pages/onboarding/register?token=' + encodeURIComponent(res.register_token || '') +
+ '&mobile_mask=' + encodeURIComponent(res.mobile_mask || '')
+ })
+ return
+ }
+ // 已有账号:复用小程序一键登录(后端按手机号认人并绑定 openid)
+ const ready = await silentLogin(code, detail.code)
+ if (!ready) return
+ await load(true)
+ await enter(card)
+ takePendingCard()
+ } catch (err) {
+ console.error('[nav] onboarding phone failed:', err)
+ uni.showToast({ title: (err && err.message) || '授权失败,请重试', icon: 'none' })
+ } finally {
+ uni.hideLoading()
+ phoneBusy.value = false
+ }
+}
+
+/** 已有账号静默登录;无企业时后端签发 tid=0 令牌,多企业时拉起企业选择抽屉 */
+async function silentLogin(code, phoneCode) {
+ const res = await loginByMiniapp({ code, phoneCode })
+ if (!res || !res.tokens || !res.tokens.access_token) {
+ throw new Error((res && res.msg) || '登录失败,请重试')
+ }
+ setAuthTokens(res.tokens)
+ clearHomeCache()
+ const list = Array.isArray(res.tenants) ? res.tenants : []
+ setTenants(list)
+ if (res.need_choose_tenant && list.length > 1) {
+ askChooseTenant(list)
+ return false
+ }
+ const t0 = list[0] || {}
+ setTid(t0.tid || 0)
+ setTenantName(t0.tenant_name || '')
+ await mergeGuestIfAny()
+ return true
}
/**
* 点击子系统卡片 → 进入该子系统独立页面组(带自己的 tabbar)。
- * 未在 config/subsystems.js 注册的模块,降级为直接打开 Web 端该模块首页。
- * 游客:命中「必须登录」模块时不直接进,先引导登录(登录后回 nav)。
+ * 需企业模块:查审核状态,未通过则引导创建 / 挂靠企业;通过后刷新企业令牌再进入。
*/
-function enter(card) {
+async function enter(card) {
if (card.locked) {
openMenu({ locked: true, title: card.name })
return
}
- if (guest.value && card.needLogin) {
- goLogin(card.name)
+ if (guest.value) {
+ uni.showToast({ title: '请先授权手机号', icon: 'none' })
return
}
+ if (card.needTenant) {
+ let st = null
+ try {
+ st = await getOnboardingStatus()
+ } catch (e) {
+ uni.showToast({ title: '状态获取失败,请重试', icon: 'none' })
+ return
+ }
+ if (st && st.has_tenant) {
+ // 审核刚通过时本地令牌可能仍是 tid=0,重新静默登录拿企业令牌
+ if (!getTid()) {
+ try {
+ const code = await navUniLoginCode()
+ const ready = await silentLogin(code, '')
+ if (!ready) return
+ await load(true)
+ } catch (e) {
+ uni.showToast({ title: '登录态刷新失败,请重试', icon: 'none' })
+ return
+ }
+ }
+ } else {
+ // 不暂存卡片:避免企业申请页返回工作台时 onShow 再次自动跳回,形成循环
+ uni.navigateTo({ url: '/pages/onboarding/company' })
+ return
+ }
+ }
if (goSubsystemByModule(card.code)) return
- if (card.items.length) {
- openMenu(card.items[0], card.name)
+ const items = card.items || []
+ if (items.length) {
+ openMenu(items[0], card.name)
return
}
if (card.path) {
@@ -411,24 +599,20 @@ function enter(card) {
uni.showToast({ title: card.name + '暂未开放', icon: 'none' })
}
-/** 引导登录:登录页登录成功后会回 nav(见 login.vue finishLogin) */
-function goLogin(name = '') {
- if (name) {
- uni.showToast({ title: '「' + name + '」需登录后使用', icon: 'none' })
- }
- uni.navigateTo({ url: '/pages/login/login' })
-}
-
/* ==================== 生命周期 ==================== */
onMounted(() => {
- // 默认落地页:未登录不再强制回登录页,而是以游客态渲染公开模块宫格
load()
})
onShow(() => {
- // 登录态可能在登录页/其他入口发生变化,每次展示重新判定并加载
+ // 注册 / 企业申请页回来后登录态可能变化,重新判定并加载
if (!loading.value) load()
+ // 注册完成后自动继续进入用户刚才点击的模块
+ const pending = takePendingCard()
+ if (pending && isLoggedIn()) {
+ setTimeout(() => enter(pending), 600)
+ }
})
@@ -627,26 +811,20 @@ onShow(() => {
color: $color-text-muted;
}
-/* 游客态:卡片左上角标出免登录 / 需登录 */
-.portal-card {
- position: relative;
-}
-.portal-tag {
- position: absolute;
- top: 14rpx;
- right: 14rpx;
- padding: 2rpx 12rpx;
- border-radius: 20rpx;
- font-size: 18rpx;
- line-height: 1.6;
-}
-.portal-tag.free {
- color: #16a34a;
- background: rgba(22, 163, 74, 0.12);
-}
-.portal-tag.need {
- color: #94a3b8;
- background: rgba(148, 163, 184, 0.16);
+/* 未授权时卡片是微信授权按钮:抹平 button 默认样式,保持与 view 卡片一致 */
+.cell-button {
+ display: block;
+ margin: 0 0 20rpx 0;
+ padding: 0 20rpx 0 0;
+ background: transparent;
+ border: none;
+ border-radius: 0;
+ line-height: normal;
+ text-align: left;
+
+ &::after {
+ border: none;
+ }
}
.bottom-space {
@@ -692,4 +870,173 @@ onShow(() => {
color: #94a3b8;
padding: 16rpx;
}
+
+/* ============ 企业选择抽屉(自绘,替代原生 actionSheet) ============ */
+.tenant-picker-mask {
+ position: fixed;
+ left: 0;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ z-index: 1000;
+ display: flex;
+ align-items: flex-end;
+ background: rgba(15, 23, 42, .48);
+ @include mask-responsive;
+}
+
+.tenant-picker-sheet {
+ width: 100%;
+ max-height: 80vh;
+ display: flex;
+ flex-direction: column;
+ background: #ffffff;
+ border-radius: 32rpx 32rpx 0 0;
+ box-shadow: 0 -12rpx 40rpx rgba(15, 23, 42, .12);
+ padding-bottom: env(safe-area-inset-bottom);
+ animation: tenant-sheet-in .22s ease-out;
+ @include sheet-responsive;
+}
+
+@keyframes tenant-sheet-in {
+ from { transform: translateY(60rpx); opacity: .6; }
+ to { transform: translateY(0); opacity: 1; }
+}
+
+.picker-grabber {
+ width: 72rpx;
+ height: 8rpx;
+ margin: 16rpx auto 4rpx;
+ border-radius: $radius-full;
+ background: #dbe3ef;
+}
+
+.picker-head {
+ display: flex;
+ align-items: center;
+ gap: 20rpx;
+ padding: 20rpx $page-padding-x 24rpx;
+ border-bottom: 1rpx solid $color-divider;
+}
+
+.picker-head-icon {
+ width: 72rpx;
+ height: 72rpx;
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 20rpx;
+ background: linear-gradient(135deg, #60a5fa, #2563eb);
+ box-shadow: 0 8rpx 18rpx rgba(37, 99, 235, .25);
+}
+
+.picker-head-copy { flex: 1; min-width: 0; }
+.picker-title { display: block; font-size: 32rpx; font-weight: 700; letter-spacing: .5rpx; color: $color-text; }
+.picker-sub { display: block; margin-top: 8rpx; font-size: 22rpx; line-height: 1.5; color: $color-text-muted; }
+.picker-close {
+ width: 56rpx;
+ height: 56rpx;
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: $radius-full;
+ background: $color-bg-page;
+}
+
+.picker-list {
+ flex: 1;
+ min-height: 0;
+ max-height: 52vh;
+ padding: 24rpx $page-padding-x 4rpx;
+ box-sizing: border-box;
+}
+
+.tenant-option {
+ display: flex;
+ align-items: center;
+ gap: 20rpx;
+ margin-bottom: 20rpx;
+ padding: 24rpx 22rpx;
+ border: 1rpx solid #e6edf8;
+ border-radius: 20rpx;
+ background: #ffffff;
+ box-shadow: 0 4rpx 16rpx rgba(38, 86, 150, .05);
+ transition: transform .15s ease, border-color .15s ease, background .15s ease;
+
+ &:active { transform: scale(.985); background: #f7faff; border-color: #b9d2ff; }
+ &.current { border-color: #bfd4ff; background: #f5f9ff; }
+ &.disabled { opacity: .55; }
+}
+
+.tenant-option-badge {
+ width: 76rpx;
+ height: 76rpx;
+ flex-shrink: 0;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 20rpx;
+ background: linear-gradient(135deg, #60a5fa, #2563eb);
+
+ &.badge-1 { background: linear-gradient(135deg, #34d399, #0ea5a4); }
+ &.badge-2 { background: linear-gradient(135deg, #a78bfa, #6366f1); }
+ &.badge-3 { background: linear-gradient(135deg, #fbbf6e, #f97316); }
+}
+
+.tenant-option-copy { flex: 1; min-width: 0; }
+.tenant-option-name {
+ display: -webkit-box;
+ line-clamp: 2;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ overflow: hidden;
+ font-size: 28rpx;
+ font-weight: 600;
+ line-height: 1.45;
+ color: $color-text;
+}
+.tenant-option-meta {
+ display: block;
+ margin-top: 8rpx;
+ font-size: 22rpx;
+ color: $color-text-muted;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+.tenant-current-tag {
+ flex-shrink: 0;
+ padding: 6rpx 16rpx;
+ border-radius: $radius-full;
+ background: $color-primary-bg;
+ font-size: 20rpx;
+ color: $color-primary;
+}
+.tenant-option-loading { flex-shrink: 0; font-size: 22rpx; color: $color-primary; }
+
+.picker-foot { padding: 8rpx $page-padding-x 20rpx; }
+.picker-cancel {
+ height: 88rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: $radius-full;
+ background: #f1f5f9;
+ font-size: 28rpx;
+ font-weight: 500;
+ color: $color-text-secondary;
+
+ &:active { background: #e6ecf5; }
+}
+.picker-hint {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 10rpx;
+ height: 72rpx;
+ font-size: 22rpx;
+ color: $color-text-muted;
+}
diff --git a/uniapp/pages/onboarding/company.vue b/uniapp/pages/onboarding/company.vue
new file mode 100644
index 0000000..e0bc8d7
--- /dev/null
+++ b/uniapp/pages/onboarding/company.vue
@@ -0,0 +1,402 @@
+
+
+
+ YUNZE WORKSPACE
+ 加入企业
+ 使用业务功能需要先创建企业,或申请挂靠已有企业
+
+
+
+
+ {{ statusTitle }}
+ {{ statusDesc }}
+
+
+
+ 创建企业
+ 挂靠企业
+
+
+
+
+
+ 企业全称 *
+
+
+
+ 统一社会信用代码 *
+
+
+
+ 法定代表人 *
+
+
+
+ 营业执照 *
+
+ {{ licenseName || '点击上传 JPG / PNG,不超过 5MB' }}
+
+
+
+ 联系人 *
+
+
+
+ 联系电话 *
+
+
+
+ 电子邮箱 *
+
+
+
+ 企业地址
+
+
+ 提交后由平台管理员审核,通过后自动成为该企业管理员
+
+
+
+
+
+ 企业全称 *
+
+
+ 搜索
+
+
+
+ {{ t.tenant_name }}
+
+
+ 未找到企业,请核对全称或改走「创建企业」
+ 已选:{{ selectedTenant.tenant_name }}
+
+
+
+ 部门 *
+
+ {{ deptName() }}
+
+
+
+ 职位 *
+
+ {{ positionName() }}
+
+
+
+ 姓名 *
+
+
+
+ 手机号
+ {{ mobileMask || '微信已验证' }}不可修改
+
+ 提交后由该企业管理员审核,通过后即可使用业务功能
+
+
+ {{ errorMsg }}
+
+ 返回工作台
+
+
+
+
+
+
diff --git a/uniapp/pages/onboarding/register.vue b/uniapp/pages/onboarding/register.vue
new file mode 100644
index 0000000..0cf4a1e
--- /dev/null
+++ b/uniapp/pages/onboarding/register.vue
@@ -0,0 +1,150 @@
+
+
+
+ YUNZE WORKSPACE
+ 设置登录密码
+ 手机号 {{ mobileMask || '已验证' }} 尚未注册,设置密码后即可使用工作台
+
+
+
+
+ 登录密码
+
+
+
+ 确认密码
+
+
+ 密码至少 6 位;微信号会在注册时自动绑定,下次可直接进入
+ {{ errorMsg }}
+
+
+
+
+
+
+
+
diff --git a/uniapp/pages/profile/profile.vue b/uniapp/pages/profile/profile.vue
index 5b7735f..78eddb7 100644
--- a/uniapp/pages/profile/profile.vue
+++ b/uniapp/pages/profile/profile.vue
@@ -6,7 +6,7 @@
{{ avatarText }}
- {{ user?.nickname || user?.name || '云泽用户' }}
+ {{ user?.nickname || user?.name || '温新云用户' }}
{{ user?.account ? `账号: ${user.account}` : `ID: ${userId}` }}
@@ -120,7 +120,7 @@ async function loadUser() {
const merged = {
...prev,
...data,
- nickname: data.name || data.nickname || data.account || prev.nickname || '云泽用户'
+ nickname: data.name || data.nickname || data.account || prev.nickname || '温新云用户'
}
setUser(merged)
user.value = merged
diff --git a/uniapp/src/styles/theme.scss b/uniapp/src/styles/theme.scss
index e892136..a40d5bf 100644
--- a/uniapp/src/styles/theme.scss
+++ b/uniapp/src/styles/theme.scss
@@ -1,5 +1,5 @@
// =============================================================
-// 云泽统一主题 · 冷调科技蓝(蓝白高级)
+// 温新云统一主题 · 冷调科技蓝(蓝白高级)
// 品牌主色 #2563eb,浅蓝 #60a5fa,深蓝 #1d4ed8
// 背景冷灰白 #f8fafc,卡片纯白,柔和多层阴影,克制留白
// =============================================================
diff --git a/uniapp/utils/auth.js b/uniapp/utils/auth.js
index 9e41a71..495a0c0 100644
--- a/uniapp/utils/auth.js
+++ b/uniapp/utils/auth.js
@@ -172,7 +172,7 @@ export function loginSuccess(payload = {}) {
id: rawUser.id,
account: rawUser.account || '',
name: rawUser.name || '',
- nickname: rawUser.nickname || rawUser.name || rawUser.account || '云泽用户',
+ nickname: rawUser.nickname || rawUser.name || rawUser.account || '温新云用户',
avatar: rawUser.avatar || '',
phone: rawUser.phone || payload.phone || '',
tid: rawUser.tid,
@@ -182,7 +182,7 @@ export function loginSuccess(payload = {}) {
}
// 保证 nickname 可用
if (!user.nickname) {
- user.nickname = user.name || user.account || '云泽用户'
+ user.nickname = user.name || user.account || '温新云用户'
}
setUser(user)
return user
diff --git a/uniapp/utils/onboarding.js b/uniapp/utils/onboarding.js
new file mode 100644
index 0000000..ad604ad
--- /dev/null
+++ b/uniapp/utils/onboarding.js
@@ -0,0 +1,30 @@
+/**
+ * 授权手机号 / 注册完成后,自动继续进入用户刚才点击的模块卡片。
+ * 只存最小字段,避免把响应式对象或函数写进 storage。
+ */
+const PENDING_KEY = 'app_pending_card'
+
+export function setPendingCard(card) {
+ if (!card) {
+ uni.removeStorageSync(PENDING_KEY)
+ return
+ }
+ uni.setStorageSync(PENDING_KEY, {
+ code: card.code || '',
+ name: card.name || '',
+ path: card.path || '',
+ needTenant: !!card.needTenant,
+ locked: !!card.locked,
+ items: (card.items || []).map((m) => ({
+ title: m.title || '',
+ path: m.path || '',
+ leaf: m.leaf !== false
+ }))
+ })
+}
+
+export function takePendingCard() {
+ const card = uni.getStorageSync(PENDING_KEY) || null
+ uni.removeStorageSync(PENDING_KEY)
+ return card
+}
diff --git a/website/src/config/sections.ts b/website/src/config/sections.ts
index f310836..92a8187 100644
--- a/website/src/config/sections.ts
+++ b/website/src/config/sections.ts
@@ -23,7 +23,7 @@ export const sections: Record = {
news: {
key: "news",
title: "新闻中心",
- subtitle: "了解云泽科技的产品动态、公司公告与行业活动。",
+ subtitle: "了解温新云科技的产品动态、公司公告与行业活动。",
basePath: "/news",
detailPath: "/news/detail",
categoryPath: "/news/category",
diff --git a/website/src/config/site.ts b/website/src/config/site.ts
index 9c84403..355c1b0 100644
--- a/website/src/config/site.ts
+++ b/website/src/config/site.ts
@@ -27,17 +27,17 @@ export interface FooterColumn {
}
export const siteInfo = reactive({
- brandMark: "YZ",
- brandName: "云泽科技",
- brandSub: "YUNZER TECH",
- fullName: "云泽科技有限公司",
- slogan: "欢迎访问云泽科技官方网站",
+ brandMark: "WX",
+ brandName: "温新云科技",
+ brandSub: "WENXIN TECH",
+ fullName: "温新云科技有限公司",
+ slogan: "欢迎访问温新云科技官方网站",
phone: "400-000-0000",
email: "contact@yunzer.com",
address: "北京市海淀区中关村软件园 25 号楼",
postalCode: "100193",
copyright:
- "COPYRIGHT (C) 2026 YUNZER TECHNOLOGY CO., LTD. ALL RIGHTS RESERVED.",
+ "COPYRIGHT (C) 2026 WENXIN TECHNOLOGY CO., LTD. ALL RIGHTS RESERVED.",
// ↓↓↓ 以下字段由「官网管理 - 站点设置」下发(对应官网模板标签) ↓↓↓
/** {yz:sitename} 站点名称 */