Compare commits

..
Author SHA1 Message Date
zlwu f343f12645 fix(mobile): 修复协议单项目校验引用导致保存中断
循环模板内字符串 ref 被 Vue 收集为数组,调用 validate 时在请求前抛错。改用函数 ref 保持单组件实例,并覆盖两角色新增、编辑及项目校验提示。关联工单 #78。
2026-11-10 09:21:29 +08:00
zlwuandCursor 1032631318 chore(mobile-h5): 微信小程序 AppID 切换为生产
将 mp-weixin.appid 改为 wx2b139581f4c855e4,与 APP_ENV=prod 对齐。

Refs: #77
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-09 16:32:24 +08:00
zlwuandCursor f9f79479e8 chore(mobile-h5): 小程序切换为生产环境配置
将 APP_ENV 设为 prod,API/OA H5/MinIO 指向 xwyd.zkxw.cn,便于发生产版。

Refs: #76
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-09 16:27:28 +08:00
4 changed files with 114 additions and 4 deletions

No files matched your search

+2 -2
View File
@@ -49,11 +49,11 @@
/* 快应用特有相关 */
"quickapp" : {},
/* 小程序特有相关 */
/* 当前:测试 AppID;切生产时把 appid 改为下方生产值 */
/* 当前:生产 AppID;切测试时把 appid 改为下方测试值 */
/* 生产 AppID:wx2b139581f4c855e4 */
/* 测试 AppID:wxa261ad85f2c99b26 */
"mp-weixin" : {
"appid" : "wxa261ad85f2c99b26",
"appid" : "wx2b139581f4c855e4",
"setting" : {
"urlCheck" : false,
"es6" : true
+1 -1
View File
@@ -6,7 +6,7 @@
*
* 微信公众平台须为对应环境配置 request / 业务域名(HTTPS)
*/
export const APP_ENV = 'test' // 'local' | 'test' | 'prod'
export const APP_ENV = 'prod' // 'local' | 'test' | 'prod'
const ENV_PRESETS = {
local: {
@@ -0,0 +1,110 @@
import test from 'node:test'
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import vm from 'node:vm'
import * as Vue from 'vue'
import { compile } from '@vue/compiler-dom'
import { projectValidation } from './project-assignment.js'
const source = readFileSync(new URL('../views/market/MarketModuleForm.vue', import.meta.url), 'utf8')
// Render the real loop/slot/ref structure; native host nodes need no browser or service.
const template = source.slice(source.indexOf('<template v-for="section in leadingSections"'), source.indexOf('\n\n <section'))
const render = new Function('Vue', compile(template, { prefixIdentifiers: true }).code)(Vue)
const submitSource = source.slice(source.indexOf('async function submit()'), source.indexOf('\nasync function confirmLeave()'))
const renderer = Vue.createRenderer({
createElement: () => ({}), createText: () => ({}), createComment: () => ({}),
insert(node, parent, anchor = null) {
parent.children ||= []
node.parent = parent
const index = parent.children.indexOf(anchor)
parent.children.splice(index < 0 ? parent.children.length : index, 0, node)
},
remove(node) {
const siblings = node.parent?.children || []
const index = siblings.indexOf(node)
if (index >= 0) siblings.splice(index, 1)
},
setText() {}, setElementText() {}, patchProp() {},
parentNode: (node) => node.parent,
nextSibling: (node) => {
const siblings = node.parent?.children || []
return siblings[siblings.indexOf(node) + 1] || null
}
})
async function mountForm({ role, mode = 'create', projectId = 'project-1', loading = false, error = '' }) {
const projectAssignment = Vue.ref(null)
const formData = Vue.ref({ source: 'research_donation', projectId })
const messages = [], requests = []
let validations = 0
const ProjectAssignmentField = {
setup(_, { expose }) {
expose({ validate() {
validations++
return projectValidation({ editing: mode === 'edit', required: true, projectId, loading, error })
} })
return () => Vue.h('div')
}
}
const Section = { setup: (_, { slots }) => () => slots.insert?.() }
const context = Vue.ref({ module: 'protocol', marketRole: role })
const app = renderer.createApp({
components: { MarketFormSection: Section, ProjectAssignmentField },
setup: () => ({
projectAssignment, context, formData, mode,
leadingSections: [{ key: 'customer' }, { key: 'protocol' }],
formOptions: {}, fieldsLocked: false, dirty: false,
sectionValue: () => ({}), searchProtocolCustomers() {}, setSectionValue() {},
uploadField() {}, handleFieldChange() {}, handleCustomerSelect() {}
}), render
})
app.mount({})
await Vue.nextTick()
const scope = {
projectAssignment, context, formData, mode: Vue.ref(mode),
submitting: Vue.ref(false), uploadPending: Vue.ref(0), isProtocolV2: Vue.ref(false),
isProtocolV1: Vue.ref(false), id: Vue.ref(mode === 'edit' ? 'record-1' : ''),
action: Vue.ref(mode), visibleSections: Vue.ref([]), dirty: Vue.ref(true),
showToast: (text) => messages.push(text), showSuccessToast: (text) => messages.push(text),
validateRequiredFields: () => '', buildMarketActionPayload: (_module, _action, data) => data,
sanitizeMarketPayload: (data) => data, buildPayload: () => formData.value,
AbortController, submitController: null, allowLeave: false,
saveMarketRecord: async (...args) => { requests.push(args); return { id: 'saved' } },
mountGuard: { isCurrent: () => true }, mountToken: 1,
resolveMarketSavedDetailId: () => 'saved', router: { replace: async () => {} },
route: { query: {} }, marketRecordNavigationQuery: () => ({}),
marketErrorMessage: (cause) => cause.message
}
const submit = vm.runInNewContext(`${submitSource}\nsubmit`, scope)
return { submit, messages, requests, app, validations: () => validations }
}
for (const role of ['specialist', 'manager']) {
for (const mode of ['create', 'edit']) {
test(`${role} ${mode}: a valid project reaches the save request`, async () => {
const form = await mountForm({ role, mode })
try {
await form.submit()
assert.equal(form.validations(), 1)
assert.equal(form.requests.length, 1)
assert.equal(form.requests[0][0].marketRole, role)
assert.deepEqual(form.messages, ['保存成功'])
} finally { form.app.unmount() }
})
}
for (const [state, message] of [
[{ projectId: '' }, '请选择所属项目'],
[{ loading: true }, '项目选项加载中,请稍后保存'],
[{ error: 'network' }, '项目选项加载失败,请重试后保存']
]) {
test(`${role}: ${message}`, async () => {
const form = await mountForm({ role, ...state })
try {
await form.submit()
assert.equal(form.validations(), 1)
assert.equal(form.requests.length, 0)
assert.deepEqual(form.messages, [message])
} finally { form.app.unmount() }
})
}
}
@@ -46,7 +46,7 @@
>
<template #insert>
<ProjectAssignmentField
ref="projectAssignment"
:ref="(instance) => (projectAssignment = instance)"
embedded
v-model="formData.projectId"
:biz-type="formData.source"