From 8b2796709b156880ae4049015d52bd7a0ac48c73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E5=8B=A4=E6=B0=91?= Date: Mon, 29 Jun 2026 11:29:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20YwxMobileApp-iOS=20=E5=88=9D=E5=A7=8B?= =?UTF-8?q?=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 三 Flavor(Shijiaobao / Clinical / Yiwangxin)iOS 项目 架构 - Shared/ 全部共用层:AppHomeView、WebMessageHandler(JSBridge)、DevUrlSettings - *Flavor/ 各 flavor 入口:AppConfig、*App(@main)、AppDelegate(Yiwangxin 含 Push) - project.yml + xcodegen 管理项目结构 SDK 集成 - 引用 Gitea SPM 包(branch: main,SNAPSHOT 开发模式) - sdk.properties 追踪当前 SDK_REF,scripts/use-sdk.sh 支持 main/local/x.y.z 一键切换 - 含各 flavor xuqm/*.xuqmconfig 配置文件 JSBridge 协议 - window.webkit.messageHandlers.SZYX_APP.postMessage(json) - 回调 window.SZYX_YWX_WebViewBridge.onMessage(json) - 实现 setTitle/getDeviceInfo/login/setUserInfo/getAppVersion/checkUpdate setStorage/getStorage/removeStorage/clearStorage/setOrientation/apiError Co-Authored-By: Claude Sonnet 4.6 --- .gitignore | 15 + Clinical/AppConfig.swift | 9 + Clinical/ClinicalApp.swift | 25 + Clinical/Info.plist | 41 + Clinical/xuqm/临床知识库.xuqmconfig | 1 + Shared/AppHomeView.swift | 460 +++++++++++ Shared/DevUrlSettings.swift | 77 ++ Shared/WebMessageHandler.swift | 270 +++++++ Shijiaobao/AppConfig.swift | 9 + Shijiaobao/Info.plist | 41 + Shijiaobao/ShijiaobaoApp.swift | 26 + Shijiaobao/xuqm/识校宝.xuqmconfig | 1 + Yiwangxin/AppConfig.swift | 9 + Yiwangxin/Info.plist | 45 ++ Yiwangxin/PushNotificationHandler.swift | 47 ++ Yiwangxin/YiwangxinApp.swift | 31 + Yiwangxin/xuqm/医网信.xuqmconfig | 1 + YwxMobileApp.xcodeproj/project.pbxproj | 721 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + project.yml | 165 ++++ scripts/use-sdk.sh | 72 ++ sdk.properties | 16 + 22 files changed, 2089 insertions(+) create mode 100644 .gitignore create mode 100644 Clinical/AppConfig.swift create mode 100644 Clinical/ClinicalApp.swift create mode 100644 Clinical/Info.plist create mode 100644 Clinical/xuqm/临床知识库.xuqmconfig create mode 100644 Shared/AppHomeView.swift create mode 100644 Shared/DevUrlSettings.swift create mode 100644 Shared/WebMessageHandler.swift create mode 100644 Shijiaobao/AppConfig.swift create mode 100644 Shijiaobao/Info.plist create mode 100644 Shijiaobao/ShijiaobaoApp.swift create mode 100644 Shijiaobao/xuqm/识校宝.xuqmconfig create mode 100644 Yiwangxin/AppConfig.swift create mode 100644 Yiwangxin/Info.plist create mode 100644 Yiwangxin/PushNotificationHandler.swift create mode 100644 Yiwangxin/YiwangxinApp.swift create mode 100644 Yiwangxin/xuqm/医网信.xuqmconfig create mode 100644 YwxMobileApp.xcodeproj/project.pbxproj create mode 100644 YwxMobileApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 project.yml create mode 100755 scripts/use-sdk.sh create mode 100644 sdk.properties diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..28200f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +# Xcode +DerivedData/ +*.xcworkspace/xcuserdata/ +*.xcodeproj/xcuserdata/ +*.ipa +*.dSYM.zip +*.dSYM +.DS_Store +xcuserdata/ + +# SPM resolved (commit for reproducible builds) +# .package-resolved <- intentionally tracked via xcodeproj + +# macOS +.DS_Store diff --git a/Clinical/AppConfig.swift b/Clinical/AppConfig.swift new file mode 100644 index 0000000..57d6efe --- /dev/null +++ b/Clinical/AppConfig.swift @@ -0,0 +1,9 @@ +import UIKit + +enum AppConfig { + static let appName = "临床知识库" + static let startUrl = "https://www.docscanfix.com/ywxdrugweb/#/" + static let enableNavBar = false + static let splashBackgroundColor = UIColor.white + static let splashImageName = "LaunchLogo" +} diff --git a/Clinical/ClinicalApp.swift b/Clinical/ClinicalApp.swift new file mode 100644 index 0000000..a34be26 --- /dev/null +++ b/Clinical/ClinicalApp.swift @@ -0,0 +1,25 @@ +import SwiftUI +import XuqmCoreSDK +import XuqmBugCollectSDK + +@main +struct ClinicalApp: App { + init() { + do { + try XuqmSDK.shared.autoInitialize(debug: _isDebugAssertConfiguration()) + } catch { + print("[ClinicalApp] XuqmSDK.autoInitialize failed: \(error)") + } + #if DEBUG + BugCollectSDK.shared.setLogLevel(.debug) + #else + BugCollectSDK.shared.setLogLevel(.warn) + #endif + } + + var body: some Scene { + WindowGroup { + AppRootView() + } + } +} diff --git a/Clinical/Info.plist b/Clinical/Info.plist new file mode 100644 index 0000000..5256480 --- /dev/null +++ b/Clinical/Info.plist @@ -0,0 +1,41 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + 临床知识库 + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + UILaunchScreen + + UILaunchScreen + + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Clinical/xuqm/临床知识库.xuqmconfig b/Clinical/xuqm/临床知识库.xuqmconfig new file mode 100644 index 0000000..b93d08d --- /dev/null +++ b/Clinical/xuqm/临床知识库.xuqmconfig @@ -0,0 +1 @@ +XUQM-CONFIG-V1.3E4aM25MBqSAk-yRkFx5Kg.yqGGwzw0y-_WlhCc.sNXviwokeueL6nxSwDlwKj6TNrHbpjAMdB9NikJut4jRJr4gG6g8AMXlajLUUBXIeWW0NhijfjKSQRORD1C-Fuf-XarlP6UKLINgc7GEFKZctf2oKlNF3k31HZReKEbApNybedkOwFHPT0T0k_4cbwNLq_tXVpZuMJ8JklKOLf4EXh9RaBHaEGmdEZMq5N2mYYaWXGRRxuV9xZKNlrbF5E3nr4z_GIRNCOEt1V2gUwbRSw4UYdV8ErMiD9jIJYpCCQQw344xfKyGpaei8rXvBw \ No newline at end of file diff --git a/Shared/AppHomeView.swift b/Shared/AppHomeView.swift new file mode 100644 index 0000000..793648d --- /dev/null +++ b/Shared/AppHomeView.swift @@ -0,0 +1,460 @@ +import SwiftUI +import XuqmCoreSDK +import XuqmWebViewSDK +import XuqmUpdateSDK +import XuqmBugCollectSDK + +// MARK: - Root view with splash overlay + +struct AppRootView: View { + @State private var showWelcome = true + + var body: some View { + ZStack { + AppHomeView(onContentReady: { showWelcome = false }) + if showWelcome { + WelcomeScreen() + .transition(.opacity) + .animation(.easeOut(duration: 0.3), value: showWelcome) + } + } + } +} + +// MARK: - Welcome / splash screen + +struct WelcomeScreen: View { + var body: some View { + Color(AppConfig.splashBackgroundColor) + .overlay( + VStack(spacing: 16) { + Image(AppConfig.splashImageName) + .resizable() + .scaledToFit() + .frame(maxWidth: 200) + } + ) + .ignoresSafeArea() + } +} + +// MARK: - Main home screen + +struct AppHomeView: View { + let onContentReady: () -> Void + + @State private var updateInfo: AppUpdateInfo? + @State private var updateBusy = false + @State private var showDevSettings = false + @State private var canGoBack = false + @State private var statusBarColor = UIColor.white + @State private var navTitle = "" + @State private var navTextColor = UIColor.black + @State private var navBgColor = UIColor.white + @State private var navGradient: [UIColor]? + @State private var navGradientAngle: Float = 0 + @State private var navBgImage: String? + @State private var pageError: String? + @State private var loadProgress: Int = 0 + @State private var ignoreVersion = false + @State private var showNotifDialog = false + + var body: some View { + VStack(spacing: 0) { + if AppConfig.enableNavBar { + navBar + if loadProgress > 0 && loadProgress < 99 { + ProgressView(value: Double(loadProgress), total: 100) + .progressViewStyle(.linear) + .tint(Color(hex: "#2196F3")) + .frame(height: 3) + } + } + + ZStack { + XWebViewView(config: XWebViewConfig( + url: DevUrlSettings.effectiveStartUrl ?? AppConfig.startUrl, + title: AppConfig.appName, + hideToolbar: true, + injectedJavaScript: webViewErrorInterceptorJS, + jsBridgeName: "SZYX_APP", + onMessage: { data in + Task { @MainActor in + handleWebMessage( + data: data, + onStatusBarColorChanged: { color in statusBarColor = color }, + onNavBarChanged: { title, textColor, bgColor, gradient, angle, image in + if AppConfig.enableNavBar { + navTitle = title + if let c = textColor { navTextColor = c } + navBgImage = image + if image != nil { + navGradient = nil + } else if let g = gradient { + navGradient = g + navGradientAngle = angle + if let c = bgColor { navBgColor = c } + } else { + navGradient = nil + if let c = bgColor { navBgColor = c } + } + } + }, + onCheckUpdateRequested: { Task { await checkUpdate(explicit: true) } }, + onOrientationChanged: { applyOrientation($0) } + ) + } + }, + onPageError: { desc in + pageError = desc + onContentReady() + BugCollectSDK.shared.captureError( + WebViewLoadError(description: desc), + tags: ["context": "webView"] + ) + }, + onProgressChanged: { pct in + loadProgress = pct + if pct >= 100 { onContentReady() } + }, + onNavigationChanged: { back, _ in canGoBack = back } + )) + + if let error = pageError { + pageErrorOverlay(error) + } + } + } + .background(Color(statusBarColor)) + .ignoresSafeArea(edges: .bottom) + .overlay(alignment: .top) { + // Match status-bar tint + Color(statusBarColor) + .frame(height: UIApplication.shared.connectedScenes + .compactMap { $0 as? UIWindowScene } + .first?.statusBarManager?.statusBarFrame.height ?? 44) + .ignoresSafeArea(edges: .top) + } + // Update dialog + .overlay { + if let info = updateInfo, info.needsUpdate { + updateOverlay(info: info) + } + } + // Notification permission prompt (yiwangxin only) + .alert("开启通知权限", isPresented: $showNotifDialog) { + Button("允许") { requestNotificationPermission() } + Button("暂不", role: .cancel) {} + } message: { + Text("需要通知权限才能接收消息推送,是否允许?") + } + .task { await startupChecks() } + #if DEBUG + .overlay(alignment: .bottomTrailing) { + Button { + showDevSettings = true + } label: { + Text("DEV") + .font(.caption2.bold()) + .padding(.horizontal, 8) + .padding(.vertical, 4) + .background(Color.orange.opacity(0.85)) + .foregroundStyle(.white) + .clipShape(Capsule()) + } + .padding(.trailing, 12) + .padding(.bottom, 32) + } + .sheet(isPresented: $showDevSettings) { + DevUrlSettingsSheet() + } + #endif + } + + // MARK: - Navigation bar + + @ViewBuilder + private var navBar: some View { + ZStack { + // Background: gradient > image > solid color + if let gradient = navGradient { + LinearGradient( + colors: gradient.map { Color($0) }, + startPoint: gradientStart(navGradientAngle), + endPoint: gradientEnd(navGradientAngle) + ) + } else if let imageName = navBgImage { + AsyncImage(url: URL(string: imageName)) { img in + img.resizable().scaledToFill() + } placeholder: { + Color(navBgColor) + } + } else { + Color(navBgColor) + } + + HStack(spacing: 0) { + if canGoBack { + Button { + XWebViewBridge.shared.currentController()?.goBack() + } label: { + Image(systemName: "chevron.left") + .foregroundColor(Color(navTextColor)) + .frame(width: 44, height: 44) + } + } + Spacer() + Text(navTitle.isEmpty ? AppConfig.appName : navTitle) + .font(.headline) + .foregroundColor(Color(navTextColor)) + Spacer() + if canGoBack { + Color.clear.frame(width: 44, height: 44) + } + } + .padding(.horizontal, 4) + } + .frame(height: 44) + .padding(.top, safeAreaTop) + } + + // MARK: - Page error + + private func pageErrorOverlay(_ error: String) -> some View { + VStack(spacing: 16) { + Text("页面加载失败") + .font(.headline) + Text(error) + .font(.caption) + .foregroundStyle(.secondary) + .multilineTextAlignment(.center) + .padding(.horizontal, 24) + Button("点击重试") { + pageError = nil + XWebViewBridge.shared.currentController()?.reload() + } + .buttonStyle(.borderedProminent) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color.white) + } + + // MARK: - Update overlay + + private func updateOverlay(info: AppUpdateInfo) -> some View { + ZStack { + Color.black.opacity(0.45).ignoresSafeArea() + VStack(alignment: .leading, spacing: 14) { + Text("发现新版本") + .font(.title3.weight(.semibold)) + Text("版本:\(info.versionName ?? String(info.versionCode ?? 0))") + .font(.subheadline.weight(.medium)) + if let log = info.changeLog, !log.isEmpty { + ScrollView { + Text(log) + .frame(maxWidth: .infinity, alignment: .leading) + .foregroundStyle(.secondary) + } + .frame(maxHeight: 160) + } + if info.forceUpdate { + Text("当前版本必须更新后才能继续使用") + .foregroundStyle(.red) + .font(.subheadline.weight(.medium)) + } + if !info.forceUpdate { + Toggle("不再提示此版本", isOn: $ignoreVersion) + .labelsHidden() + Text("不再提示此版本").font(.subheadline) + } + if updateBusy { + ProgressView() + } + HStack(spacing: 12) { + if !info.forceUpdate { + Button("暂不更新") { + if ignoreVersion, let vc = info.versionCode { + UpdateSDK.shared.ignoreVersion(vc) + } + updateInfo = nil + } + .buttonStyle(.bordered) + } + Button(updateButtonLabel(info: info)) { + openUpdate(info: info) + } + .buttonStyle(.borderedProminent) + .disabled(updateBusy) + } + } + .padding(20) + .frame(maxWidth: 420) + .background(.ultraThinMaterial, in: RoundedRectangle(cornerRadius: 20, style: .continuous)) + .padding(.horizontal, 24) + } + } + + // MARK: - Startup checks + + private func startupChecks() async { + #if PUSH_ENABLED + checkNotificationPermission() + #endif + await checkUpdate(explicit: false) + } + + private func checkUpdate(explicit: Bool) async { + do { + let info = try await UpdateSDK.shared.checkAppUpdate(bypassIgnore: explicit) + if info.needsUpdate { updateInfo = info } + else if explicit { + await MainActor.run { + showToast("当前已是最新版本") + } + } + } catch { + BugCollectSDK.shared.captureError(error, tags: ["context": "checkAppUpdate"]) + } + } + + private func openUpdate(info: AppUpdateInfo) { + if let storeUrl = info.appStoreUrl, !storeUrl.isEmpty { + UpdateSDK.shared.openAppStore(url: storeUrl) + if !info.forceUpdate { updateInfo = nil } + } else if let marketUrl = info.marketUrl, !marketUrl.isEmpty { + UpdateSDK.shared.openAppStore(url: marketUrl) + if !info.forceUpdate { updateInfo = nil } + } + } + + private func updateButtonLabel(info: AppUpdateInfo) -> String { + let hasStore = (info.appStoreUrl?.isEmpty == false) || (info.marketUrl?.isEmpty == false) + return hasStore ? "前往商店" : "更新" + } + + // MARK: - Orientation + + private func applyOrientation(_ orientation: String) { + guard let scene = UIApplication.shared.connectedScenes + .compactMap({ $0 as? UIWindowScene }).first else { return } + let mask: UIInterfaceOrientationMask = switch orientation { + case "portrait": .portrait + case "landscape": .landscape + case "landscapeLeft": .landscapeLeft + case "landscapeRight": .landscapeRight + case "auto": .all + default: .all + } + scene.requestGeometryUpdate(.iOS(interfaceOrientations: mask)) + } + + // MARK: - Notification permission + + private func checkNotificationPermission() { + Task { + let center = UNUserNotificationCenter.current() + let settings = await center.notificationSettings() + if settings.authorizationStatus == .notDetermined { + await MainActor.run { showNotifDialog = true } + } + } + } + + private func requestNotificationPermission() { + Task { + try? await UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .badge, .sound]) + await UIApplication.shared.registerForRemoteNotifications() + } + } + + // MARK: - Helpers + + private var safeAreaTop: CGFloat { + UIApplication.shared.connectedScenes + .compactMap { $0 as? UIWindowScene } + .first?.windows.first?.safeAreaInsets.top ?? 44 + } + + private func gradientStart(_ angle: Float) -> UnitPoint { + let rad = Double(angle) * .pi / 180 + return UnitPoint(x: 0.5 - sin(rad) * 0.5, y: 0.5 - cos(rad) * 0.5) + } + + private func gradientEnd(_ angle: Float) -> UnitPoint { + let rad = Double(angle) * .pi / 180 + return UnitPoint(x: 0.5 + sin(rad) * 0.5, y: 0.5 + cos(rad) * 0.5) + } + + private func showToast(_ message: String) { + // Simple alert-style toast via a brief UIAlertController presentation + guard let scene = UIApplication.shared.connectedScenes.compactMap({ $0 as? UIWindowScene }).first, + let vc = scene.windows.first?.rootViewController else { return } + let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert) + vc.present(alert, animated: true) + DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { alert.dismiss(animated: true) } + } +} + +// MARK: - WebView error interceptor JS (mirrors Android's WEBVIEW_ERROR_INTERCEPTOR_JS) + +private let webViewErrorInterceptorJS = """ +(function() { + var bridge = window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.SZYX_APP; + if (!bridge) return; + + function report(url, method, status, statusText, body) { + try { + bridge.postMessage(JSON.stringify({ + model: 'apiError', + data: { url: url, method: method, status: status, statusText: statusText || '', response: (body || '').substring(0, 500) } + })); + } catch(e) {} + } + + var origFetch = window.fetch; + window.fetch = function() { + var url = arguments[0] && (typeof arguments[0] === 'string' ? arguments[0] : arguments[0].url) || ''; + var method = (arguments[1] && arguments[1].method) || 'GET'; + return origFetch.apply(this, arguments).then(function(resp) { + if (!resp.ok) { + resp.clone().text().then(function(body) { report(url, method, resp.status, resp.statusText, body); }); + } + return resp; + }); + }; + + var origOpen = XMLHttpRequest.prototype.open; + var origSend = XMLHttpRequest.prototype.send; + XMLHttpRequest.prototype.open = function(m, u) { this._m = m; this._u = u; return origOpen.apply(this, arguments); }; + XMLHttpRequest.prototype.send = function() { + var xhr = this; + xhr.addEventListener('load', function() { + if (xhr.status >= 400) report(xhr._u, xhr._m, xhr.status, xhr.statusText, xhr.responseText); + }); + xhr.addEventListener('error', function() { report(xhr._u, xhr._m, 0, 'Network Error', ''); }); + return origSend.apply(this, arguments); + }; +})(); +""" + +// MARK: - Supporting error type + +private struct WebViewLoadError: Error { + let description: String + var localizedDescription: String { description } +} + +// MARK: - Color hex init + +private extension Color { + init(hex: String) { + if let c = parseHexColor(hex) { + self = Color(c) + } else { + self = .clear + } + } +} + +// MARK: - UserNotifications import (only needed at call sites above) +import UserNotifications diff --git a/Shared/DevUrlSettings.swift b/Shared/DevUrlSettings.swift new file mode 100644 index 0000000..f0a9fe0 --- /dev/null +++ b/Shared/DevUrlSettings.swift @@ -0,0 +1,77 @@ +import SwiftUI + +private let kDevUrlKey = "dev_custom_start_url" + +/// Debug-only URL switcher. Mirrors Android's DevUrlSettingsActivity. +/// Production builds should never show or use this. +enum DevUrlSettings { + static var effectiveStartUrl: String? { + #if DEBUG + return UserDefaults.standard.string(forKey: kDevUrlKey)?.nonEmpty + #else + return nil + #endif + } + + static func save(_ url: String) { + UserDefaults.standard.set(url.trimmingCharacters(in: .whitespaces), forKey: kDevUrlKey) + } + + static func clear() { + UserDefaults.standard.removeObject(forKey: kDevUrlKey) + } +} + +#if DEBUG +struct DevUrlSettingsSheet: View { + @Environment(\.dismiss) private var dismiss + @State private var input: String = DevUrlSettings.effectiveStartUrl ?? "" + + var body: some View { + NavigationStack { + Form { + Section { + TextField("自定义 URL(留空使用默认)", text: $input, axis: .vertical) + .keyboardType(.URL) + .autocorrectionDisabled() + .textInputAutocapitalization(.never) + .lineLimit(4) + } header: { + Text("调试用后端地址") + } footer: { + Text("设置后重启 App 生效。留空则使用 AppConfig.startUrl。") + } + + Section { + Button("使用默认地址") { + input = "" + } + .foregroundStyle(.secondary) + } + } + .navigationTitle("开发环境设置") + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .confirmationAction) { + Button("保存") { + let trimmed = input.trimmingCharacters(in: .whitespaces) + if trimmed.isEmpty { + DevUrlSettings.clear() + } else { + DevUrlSettings.save(trimmed) + } + dismiss() + } + } + ToolbarItem(placement: .cancellationAction) { + Button("取消") { dismiss() } + } + } + } + } +} +#endif + +private extension String { + var nonEmpty: String? { isEmpty ? nil : self } +} diff --git a/Shared/WebMessageHandler.swift b/Shared/WebMessageHandler.swift new file mode 100644 index 0000000..3d9f6a7 --- /dev/null +++ b/Shared/WebMessageHandler.swift @@ -0,0 +1,270 @@ +import Foundation +import UIKit +import XuqmCoreSDK +import XuqmWebViewSDK +import XuqmBugCollectSDK + +/// H5 本地存储的 UserDefaults suite 文件名,与 Update SDK 隔离 +private let H5StorageSuite = "szyx_h5_storage" + +/// H5 能感知的最高 App API 版本 +private let APP_API_VERSION = 4 + +// MARK: - Message Dispatcher + +@MainActor +func handleWebMessage( + data: String, + onStatusBarColorChanged: ((UIColor) -> Void)? = nil, + onNavBarChanged: ((String, UIColor?, UIColor?, [UIColor]?, Float, String?) -> Void)? = nil, + onCheckUpdateRequested: (() -> Void)? = nil, + onOrientationChanged: ((String) -> Void)? = nil +) { + do { + guard let jsonData = data.data(using: .utf8), + let json = try JSONSerialization.jsonObject(with: jsonData) as? [String: Any] else { return } + + let model = json["model"] as? String ?? "" + let functionId = json["functionId"] as? String ?? "" + + switch model { + case "setTitle": + handleSetTitle(json: json, functionId: functionId, + onStatusBarColorChanged: onStatusBarColorChanged, + onNavBarChanged: onNavBarChanged) + + case "isWechatInstalled": + // WeChat is never installed on iOS (App Store policy) + respondToWeb(functionId: functionId, model: model, status: "0", + message: "isWechatInstalled:ok", + value: ["isWechatInstalled": false]) + + case "wechatPay": + // WeChat Pay is not supported in this build + respondToWeb(functionId: functionId, model: model, + status: "E50001", message: "当前App不支持微信支付") + + case "getDeviceInfo": + let device = UIDevice.current + let deviceInfo: [String: Any] = [ + "osName": "iOS", + "osVersion": "iOS \(device.systemVersion)", + "baseVersion": device.systemVersion, + "brand": "Apple", + "manufacturer": "Apple", + "model": device.model, + "marketName": device.name, + "deviceId": device.identifierForVendor?.uuidString ?? "", + ] + respondToWeb(functionId: functionId, model: model, status: "0", + message: "getDeviceInfo:ok", value: ["deviceInfo": deviceInfo]) + + case "login": + let userId = json["userId"] as? String ?? "" + if !userId.isEmpty { + let userSig = (json["userSig"] as? String)?.nonEmpty + let name = (json["name"] as? String)?.nonEmpty + let avatar = (json["avatar"] as? String)?.nonEmpty + XuqmSDK.shared.setUserInfo(userId: userId, nickname: name, avatar: avatar) + if let userSig { + Task { await XuqmSDK.shared.login(userId: userId, userSig: userSig) } + } + BugCollectSDK.shared.addBreadcrumb(category: "auth", message: "login userId=\(userId)") + } + respondOk(functionId: functionId, model: model, message: "login:ok") + + case "setUserInfo": + let userId = ((json["userId"] as? String)?.nonEmpty ?? (json["id"] as? String)?.nonEmpty) ?? "" + if !userId.isEmpty { + let userSig = (json["userSig"] as? String)?.nonEmpty + let name = (json["name"] as? String)?.nonEmpty + let avatar = (json["avatar"] as? String)?.nonEmpty + XuqmSDK.shared.setUserInfo(userId: userId, nickname: name, avatar: avatar) + if let userSig { + Task { await XuqmSDK.shared.login(userId: userId, userSig: userSig) } + } + } + respondOk(functionId: functionId, model: model, message: "setUserInfo:ok") + + case "getAppVersion": + let versionName = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "1.0.0" + respondToWeb(functionId: functionId, model: model, status: "0", + message: "getAppVersion:ok", + value: ["version": APP_API_VERSION, "appVersion": versionName]) + + case "checkUpdate": + onCheckUpdateRequested?() + respondOk(functionId: functionId, model: model, message: "checkUpdate:ok") + + case "setStorage": + let storage = json["storage"] as? [String: Any] + let key = (storage?["key"] as? String) ?? "" + let value = (storage?["value"] as? String) ?? "" + if !key.isEmpty { + let defaults = UserDefaults(suiteName: H5StorageSuite) + defaults?.set(value, forKey: key) + } + respondOk(functionId: functionId, model: model, message: "setStorage:ok") + + case "getStorage": + let storage = json["storage"] as? [String: Any] + let key = (storage?["key"] as? String) ?? "" + let defaultValue = (storage?["defaultValue"] as? String) ?? "" + let stored: String + if !key.isEmpty, let v = UserDefaults(suiteName: H5StorageSuite)?.string(forKey: key) { + stored = v + } else { + stored = defaultValue + } + respondToWeb(functionId: functionId, model: model, status: "0", + message: "getStorage:ok", + value: ["storage": ["key": key, "value": stored]]) + + case "removeStorage": + let key = (json["storage"] as? [String: Any])?["key"] as? String ?? "" + if !key.isEmpty { UserDefaults(suiteName: H5StorageSuite)?.removeObject(forKey: key) } + respondOk(functionId: functionId, model: model, message: "removeStorage:ok") + + case "clearStorage": + if let d = UserDefaults(suiteName: H5StorageSuite) { + d.dictionaryRepresentation().keys.forEach { d.removeObject(forKey: $0) } + } + respondOk(functionId: functionId, model: model, message: "clearStorage:ok") + + case "setOrientation": + let orientation = json["orientation"] as? String ?? "" + onOrientationChanged?(orientation) + respondOk(functionId: functionId, model: model, message: "setOrientation:ok") + + case "apiError": + let d = (json["data"] as? [String: Any]) ?? json + let url = d["url"] as? String ?? "" + let method = d["method"] as? String ?? "" + let status = d["status"] as? Int ?? 0 + let statusText = d["statusText"] as? String ?? "" + let body = d["response"] as? String ?? "" + BugCollectSDK.shared.captureError( + ApiCallError(method: method, url: url, status: status, statusText: statusText, body: body), + tags: ["context": "webViewApi", "url": url, "method": method, "status": status] + ) + + default: + respondToWeb(functionId: functionId, model: model, + status: "E40001", message: "当前App版本不支持该接口") + } + } catch { + BugCollectSDK.shared.captureError(error, tags: ["context": "handleWebMessage"]) + } +} + +// MARK: - setTitle helpers + +private func handleSetTitle( + json: [String: Any], + functionId: String, + onStatusBarColorChanged: ((UIColor) -> Void)?, + onNavBarChanged: ((String, UIColor?, UIColor?, [UIColor]?, Float, String?) -> Void)? +) { + let titleObj = json["title"] as? [String: Any] + let titleText = titleObj?["titleText"] as? String ?? "" + let textColor = parseHexColor(titleObj?["titleTextColor"] as? String) + let bgImage = (titleObj?["titleBackgroundImage"] as? String)?.nonEmpty + + let gradientArr = titleObj?["titleBackgroundGradient"] as? [String] + let bgGradient: [UIColor]? + let bgColor: UIColor? + if let gradientArr, gradientArr.count >= 2 { + bgGradient = gradientArr.compactMap { parseHexColor($0) } + bgColor = bgGradient?.first + } else { + bgGradient = nil + bgColor = parseHexColor(titleObj?["titleBackgroundColor"] as? String) + } + let gradientAngle = (titleObj?["titleBackgroundGradientAngle"] as? Double).map { Float($0) } ?? 0 + + if let bgColor { onStatusBarColorChanged?(bgColor) } + onNavBarChanged?(titleText, textColor, bgColor, bgGradient, gradientAngle, bgImage) + respondOk(functionId: functionId, model: "setTitle", message: "setTitle:ok") +} + +// MARK: - Response helpers + +private func respondOk(functionId: String, model: String, message: String) { + respondToWeb(functionId: functionId, model: model, status: "0", message: message, value: [:]) +} + +private func respondToWeb( + functionId: String, + model: String, + status: String = "0", + message: String, + value: [String: Any] = [:] +) { + var response: [String: Any] = [ + "functionId": functionId, + "model": model, + "status": status, + "message": message, + "value": value, + ] + guard let data = try? JSONSerialization.data(withJSONObject: response), + let json = String(data: data, encoding: .utf8) else { return } + + let js = "(function(){window.SZYX_YWX_WebViewBridge&&window.SZYX_YWX_WebViewBridge.onMessage(\(json));})()" + postJSToWebView(js) +} + +// MARK: - Color helpers + +func parseHexColor(_ hex: String?) -> UIColor? { + guard var h = hex?.trimmingCharacters(in: .whitespaces), !h.isEmpty else { return nil } + if h.hasPrefix("#") { h = String(h.dropFirst()) } + guard h.count == 6 || h.count == 8 else { return nil } + var value: UInt64 = 0 + Scanner(string: h).scanHexInt64(&value) + if h.count == 6 { + return UIColor( + red: CGFloat((value >> 16) & 0xFF) / 255, + green: CGFloat((value >> 8) & 0xFF) / 255, + blue: CGFloat( value & 0xFF) / 255, + alpha: 1 + ) + } else { + return UIColor( + red: CGFloat((value >> 24) & 0xFF) / 255, + green: CGFloat((value >> 16) & 0xFF) / 255, + blue: CGFloat((value >> 8) & 0xFF) / 255, + alpha: CGFloat( value & 0xFF) / 255 + ) + } +} + +func isLightColor(_ color: UIColor) -> Bool { + var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0 + color.getRed(&r, green: &g, blue: &b, alpha: nil) + return 0.299 * r + 0.587 * g + 0.114 * b > 0.5 +} + +// MARK: - JS posting + +func postJSToWebView(_ js: String) { + // Forward via XWebViewBridge to the active WKWebView + Task { @MainActor in + XWebViewBridge.shared.postMessageToWeb(js) + } +} + +// MARK: - Supporting types + +private struct ApiCallError: Error { + let method: String + let url: String + let status: Int + let statusText: String + let body: String + var localizedDescription: String { "API \(method) \(url) → \(status) \(statusText)" } +} + +private extension String { + var nonEmpty: String? { isEmpty ? nil : self } +} diff --git a/Shijiaobao/AppConfig.swift b/Shijiaobao/AppConfig.swift new file mode 100644 index 0000000..54c4311 --- /dev/null +++ b/Shijiaobao/AppConfig.swift @@ -0,0 +1,9 @@ +import UIKit + +enum AppConfig { + static let appName = "识校宝" + static let startUrl = "https://www.docscanfix.com/ai-h5/index.html#/pages/home/loadCommon?needLogin=true" + static let enableNavBar = false + static let splashBackgroundColor = UIColor.white + static let splashImageName = "LaunchLogo" +} diff --git a/Shijiaobao/Info.plist b/Shijiaobao/Info.plist new file mode 100644 index 0000000..2e625f2 --- /dev/null +++ b/Shijiaobao/Info.plist @@ -0,0 +1,41 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + 识校宝 + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + UILaunchScreen + + UILaunchScreen + + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Shijiaobao/ShijiaobaoApp.swift b/Shijiaobao/ShijiaobaoApp.swift new file mode 100644 index 0000000..d6de817 --- /dev/null +++ b/Shijiaobao/ShijiaobaoApp.swift @@ -0,0 +1,26 @@ +import SwiftUI +import XuqmCoreSDK +import XuqmBugCollectSDK + +@main +struct ShijiaobaoApp: App { + init() { + do { + try XuqmSDK.shared.autoInitialize(debug: _isDebugAssertConfiguration()) + } catch { + // autoInitialize failed — BugCollect not available yet, print to console + print("[ShijiaobaoApp] XuqmSDK.autoInitialize failed: \(error)") + } + #if DEBUG + BugCollectSDK.shared.setLogLevel(.debug) + #else + BugCollectSDK.shared.setLogLevel(.warn) + #endif + } + + var body: some Scene { + WindowGroup { + AppRootView() + } + } +} diff --git a/Shijiaobao/xuqm/识校宝.xuqmconfig b/Shijiaobao/xuqm/识校宝.xuqmconfig new file mode 100644 index 0000000..98a03b1 --- /dev/null +++ b/Shijiaobao/xuqm/识校宝.xuqmconfig @@ -0,0 +1 @@ +XUQM-CONFIG-V1.-2GvTdXBfg9CxON5TYjcXQ.YMRMCkUL-ngjgqWf.ZrGxpahz0fKA0VmfVSExV-g7aO7a2105RvzzgjnNEEeQ0mN_pl4himQf3Q83HCaYtMBfeH3i3WPocTlHsRhXqGTusIWd5x6aByKEwmpjfGGsKb-CGEH4PKe7hGgx3VzXxvvVy28ci9_1srgVmpqSDnF8b0K7nQPECFA-0N_zVXjGcOqHlEEts3piYE_RawyhUGvFXUhV0FsMpHQ6Fpx0VEZd-6UG191WNWiNjlajF3Phi0fvJtNBYSIbqeYqF5NxJvFi7UQubqNcpKxZ6ru06XeaemnIUxO_iT0VkPmzi8jLOINSExYH3naf4ReMs3qamsYx_aTcHxaXtqCjCtnnsPJ6zJsac2ccel9CG0rARi3K_cBDL9mBeGJ5oRV-hR2847g \ No newline at end of file diff --git a/Yiwangxin/AppConfig.swift b/Yiwangxin/AppConfig.swift new file mode 100644 index 0000000..a2ac680 --- /dev/null +++ b/Yiwangxin/AppConfig.swift @@ -0,0 +1,9 @@ +import UIKit + +enum AppConfig { + static let appName = "医网信" + static let startUrl = "https://frp.xuqinmin.com/h5/" + static let enableNavBar = false + static let splashBackgroundColor = UIColor.white + static let splashImageName = "LaunchLogo" +} diff --git a/Yiwangxin/Info.plist b/Yiwangxin/Info.plist new file mode 100644 index 0000000..3e4106e --- /dev/null +++ b/Yiwangxin/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + 医网信 + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(MARKETING_VERSION) + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSAppTransportSecurity + + NSAllowsArbitraryLoads + + + UIBackgroundModes + + remote-notification + + UILaunchScreen + + UILaunchScreen + + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/Yiwangxin/PushNotificationHandler.swift b/Yiwangxin/PushNotificationHandler.swift new file mode 100644 index 0000000..d64f820 --- /dev/null +++ b/Yiwangxin/PushNotificationHandler.swift @@ -0,0 +1,47 @@ +import UIKit +import XuqmCoreSDK +import XuqmWebViewSDK +import XuqmPushSDK + +final class AppDelegate: NSObject, UIApplicationDelegate, PushMessageDelegate, @unchecked Sendable { + + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + PushSDK.shared.delegate = self + return true + } + + func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { + XuqmSDK.shared.registerDeviceToken(deviceToken) + } + + func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { + print("[Push] Failed to register for remote notifications: \(error)") + } + + // MARK: - PushMessageDelegate + + func pushSDK(_ sdk: PushSDK, didReceiveMessage message: PushMessage) { + // Foreground notification: post to JS if WebView is active + if let payload = try? JSONSerialization.data(withJSONObject: message.payload), + let json = String(data: payload, encoding: .utf8) { + let js = "window.SZYX_YWX_WebViewBridge&&window.SZYX_YWX_WebViewBridge.onPushMessage(\(json))" + Task { @MainActor in + XWebViewBridge.shared.postMessageToWeb(js) + } + } + } + + func pushSDK(_ sdk: PushSDK, didTapNotification message: PushMessage) { + // User tapped notification — navigate to relevant page via JS bridge + if let payload = try? JSONSerialization.data(withJSONObject: message.payload), + let json = String(data: payload, encoding: .utf8) { + let js = "window.SZYX_YWX_WebViewBridge&&window.SZYX_YWX_WebViewBridge.onPushTapped(\(json))" + Task { @MainActor in + XWebViewBridge.shared.postMessageToWeb(js) + } + } + } +} diff --git a/Yiwangxin/YiwangxinApp.swift b/Yiwangxin/YiwangxinApp.swift new file mode 100644 index 0000000..39f8267 --- /dev/null +++ b/Yiwangxin/YiwangxinApp.swift @@ -0,0 +1,31 @@ +import SwiftUI +import XuqmCoreSDK +import XuqmBugCollectSDK +import XuqmPushSDK + +@main +struct YiwangxinApp: App { + @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + + init() { + do { + try XuqmSDK.shared.autoInitialize(debug: _isDebugAssertConfiguration()) + } catch { + print("[YiwangxinApp] XuqmSDK.autoInitialize failed: \(error)") + } + #if DEBUG + BugCollectSDK.shared.setLogLevel(.debug) + #else + BugCollectSDK.shared.setLogLevel(.warn) + #endif + Task { @MainActor in + try? await PushSDK.shared.start() + } + } + + var body: some Scene { + WindowGroup { + AppRootView() + } + } +} diff --git a/Yiwangxin/xuqm/医网信.xuqmconfig b/Yiwangxin/xuqm/医网信.xuqmconfig new file mode 100644 index 0000000..1eea051 --- /dev/null +++ b/Yiwangxin/xuqm/医网信.xuqmconfig @@ -0,0 +1 @@ +XUQM-CONFIG-V1.segVVy8WenVbcLDPBvKMYA.DnKBEJoaqjROLJ77.33eQzNgOjQtsVGyuTCEqRnTXYPhMkAqsHIAr4xOsImbGsdTypz8yjd3xNEWDE8WBXbw0gp5ZWJhHxKbS2V86JC5jn780kDkQAz7fWWuz0F7nYK4IJdM0BroGp8n7P6F5HmFm9q3qSUUATYUewFw_sJzP2rcuP21qUErxDANq1N1G6YF7dH-oslmggaKFpNfUF8pvRFL-I1s2xflf-tR6H_PPNnoE5qQmzPFeGY885uixY56cem05lnZhcRgC-HMNN9HwH4eToJ5fZ3YRKaEh8Xo32VeSxjvRHzhHmby_NMzc55I7qfuXp0Ce-ud1Am2zU2MNtaXqLKVIxs1aucVthANXM3nFZ8VG4sy9wj4DiDn2SZb01QY3SeLa4UMzPqGde8HnE5-tsW8vh4n8QRjqY7-2 \ No newline at end of file diff --git a/YwxMobileApp.xcodeproj/project.pbxproj b/YwxMobileApp.xcodeproj/project.pbxproj new file mode 100644 index 0000000..347ed02 --- /dev/null +++ b/YwxMobileApp.xcodeproj/project.pbxproj @@ -0,0 +1,721 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 77; + objects = { + +/* Begin PBXBuildFile section */ + 048663940A87F400EAF53D8F /* WebMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD20ED616BC8974D67BB37AB /* WebMessageHandler.swift */; }; + 07930E6D06F64D799554D770 /* WebMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD20ED616BC8974D67BB37AB /* WebMessageHandler.swift */; }; + 090E9A24EB09B39C7A10EBF8 /* 识校宝.xuqmconfig in Resources */ = {isa = PBXBuildFile; fileRef = A6878AFA85D5F976AD1CCCEA /* 识校宝.xuqmconfig */; }; + 0978556633B199C6BD1819B4 /* XuqmUpdateSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 80FE5716BCB8DE96E9FA44F3 /* XuqmUpdateSDK */; }; + 14222A448C4408CCEBE61891 /* AppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 267FC0968185FCF9E8283018 /* AppConfig.swift */; }; + 16B20ECD6495EB91694A2BD1 /* XuqmWebViewSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 06B237FA7AA8B805CBB8CE5C /* XuqmWebViewSDK */; }; + 18BC8D2728EBFBCE8AA0529B /* XuqmBugCollectSDK in Frameworks */ = {isa = PBXBuildFile; productRef = F02302DCF2AB48FD75CA6050 /* XuqmBugCollectSDK */; }; + 1BC190112E6A65B141D54675 /* PushNotificationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = D2B6E7E7EB699C0C0EC012E3 /* PushNotificationHandler.swift */; }; + 1E7A88E490CDFDEB0A3188FB /* DevUrlSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCC2FCA7B6919C2158639A1 /* DevUrlSettings.swift */; }; + 46B250AEADA8EC95943857C3 /* 医网信.xuqmconfig in Resources */ = {isa = PBXBuildFile; fileRef = C8E1DBEA3AAED25B6E99516B /* 医网信.xuqmconfig */; }; + 4FBCDF6B8B8B1985C5274D86 /* ClinicalApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8E26B616F9150DC3C4471F86 /* ClinicalApp.swift */; }; + 5B24C7A49A491C06BE84C2CE /* XuqmCoreSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 73D91AAAD0801E58CCBE0122 /* XuqmCoreSDK */; }; + 7262F4ABAB6A240FBAF64307 /* DevUrlSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCC2FCA7B6919C2158639A1 /* DevUrlSettings.swift */; }; + 83F63C4BB4677A08A2B55AA1 /* XuqmCoreSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 89FA3FE5B67B797F890612E0 /* XuqmCoreSDK */; }; + 9648083CE94F5DC9EA4D2BAF /* XuqmWebViewSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 410DC73E7A3A6C59E0270F87 /* XuqmWebViewSDK */; }; + A025F926434AAFEBE9713898 /* WebMessageHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD20ED616BC8974D67BB37AB /* WebMessageHandler.swift */; }; + A6125511E046A1E7BAC9E768 /* 临床知识库.xuqmconfig in Resources */ = {isa = PBXBuildFile; fileRef = D6D139462BF98DD4383C3A2F /* 临床知识库.xuqmconfig */; }; + AEA5E916B8FDEE608E8B2924 /* AppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9424D4F36BD03EBEB32DD1BE /* AppConfig.swift */; }; + B13A671EADB8C936B2C8D585 /* AppHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35BAEDD568654DD797878F59 /* AppHomeView.swift */; }; + BD981CB0B56FDF0A9D350861 /* XuqmUpdateSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 3970C3CDC46BF50BBB1C661D /* XuqmUpdateSDK */; }; + C449D9323499A54C82267545 /* YiwangxinApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = DC4F5611E16A156CE89BDC84 /* YiwangxinApp.swift */; }; + C53AC097BFA52C72071AC7FC /* XuqmWebViewSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 63206297B881509DB98EC62D /* XuqmWebViewSDK */; }; + DAB7C230FE6B413288DDDEC0 /* XuqmBugCollectSDK in Frameworks */ = {isa = PBXBuildFile; productRef = B83686FDA7C16BCD559EF954 /* XuqmBugCollectSDK */; }; + DF14B714F9B3BD4338080FC3 /* DevUrlSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CCC2FCA7B6919C2158639A1 /* DevUrlSettings.swift */; }; + E512D7F10071CFB4C5807BAD /* XuqmBugCollectSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 6425A5C3C87BE6A40511961F /* XuqmBugCollectSDK */; }; + E531EC5A0DDB7F4AC115B7B3 /* XuqmCoreSDK in Frameworks */ = {isa = PBXBuildFile; productRef = B988767C8936E6E2FD97F9F7 /* XuqmCoreSDK */; }; + E8DCFDB7174976BBD889E0A0 /* AppConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3DBA1791BE866D08A7349178 /* AppConfig.swift */; }; + F594A24125851013F8EFC1CF /* XuqmUpdateSDK in Frameworks */ = {isa = PBXBuildFile; productRef = DD1BD16A1DAFCCCF600D4965 /* XuqmUpdateSDK */; }; + F8824380AE3739CF807A378E /* AppHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35BAEDD568654DD797878F59 /* AppHomeView.swift */; }; + FA739856F3820C503E9805EC /* XuqmPushSDK in Frameworks */ = {isa = PBXBuildFile; productRef = 683C876CC716649F3640F88E /* XuqmPushSDK */; }; + FCAC187B65C48424E1492469 /* ShijiaobaoApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 379EB60737598F6B2416A75D /* ShijiaobaoApp.swift */; }; + FE071409934D229E6BBA9D4C /* AppHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 35BAEDD568654DD797878F59 /* AppHomeView.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 267FC0968185FCF9E8283018 /* AppConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfig.swift; sourceTree = ""; }; + 35BAEDD568654DD797878F59 /* AppHomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppHomeView.swift; sourceTree = ""; }; + 379EB60737598F6B2416A75D /* ShijiaobaoApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShijiaobaoApp.swift; sourceTree = ""; }; + 3CBEF42AAD17F5096C7D15A5 /* Clinical.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = Clinical.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 3CCC2FCA7B6919C2158639A1 /* DevUrlSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DevUrlSettings.swift; sourceTree = ""; }; + 3DBA1791BE866D08A7349178 /* AppConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfig.swift; sourceTree = ""; }; + 5430EE446D97FF9C6E9D260C /* Shijiaobao.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = Shijiaobao.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 6B5DFE7FA7D60F730A58B71B /* Yiwangxin.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = Yiwangxin.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 6E46F5314B429E382CA10453 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 8E26B616F9150DC3C4471F86 /* ClinicalApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClinicalApp.swift; sourceTree = ""; }; + 9424D4F36BD03EBEB32DD1BE /* AppConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfig.swift; sourceTree = ""; }; + 9FAE13B6C156C664F7F944DB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + A6878AFA85D5F976AD1CCCEA /* 识校宝.xuqmconfig */ = {isa = PBXFileReference; path = "识校宝.xuqmconfig"; sourceTree = ""; }; + BD20ED616BC8974D67BB37AB /* WebMessageHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebMessageHandler.swift; sourceTree = ""; }; + BE4652CBDE3972A23969FF69 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + C8E1DBEA3AAED25B6E99516B /* 医网信.xuqmconfig */ = {isa = PBXFileReference; path = "医网信.xuqmconfig"; sourceTree = ""; }; + D2B6E7E7EB699C0C0EC012E3 /* PushNotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationHandler.swift; sourceTree = ""; }; + D6D139462BF98DD4383C3A2F /* 临床知识库.xuqmconfig */ = {isa = PBXFileReference; path = "临床知识库.xuqmconfig"; sourceTree = ""; }; + DC4F5611E16A156CE89BDC84 /* YiwangxinApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YiwangxinApp.swift; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 19375A6390BDBFC6185E21EC /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 5B24C7A49A491C06BE84C2CE /* XuqmCoreSDK in Frameworks */, + 9648083CE94F5DC9EA4D2BAF /* XuqmWebViewSDK in Frameworks */, + BD981CB0B56FDF0A9D350861 /* XuqmUpdateSDK in Frameworks */, + 18BC8D2728EBFBCE8AA0529B /* XuqmBugCollectSDK in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A06FF5C43835077B8F1868B8 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 83F63C4BB4677A08A2B55AA1 /* XuqmCoreSDK in Frameworks */, + C53AC097BFA52C72071AC7FC /* XuqmWebViewSDK in Frameworks */, + 0978556633B199C6BD1819B4 /* XuqmUpdateSDK in Frameworks */, + DAB7C230FE6B413288DDDEC0 /* XuqmBugCollectSDK in Frameworks */, + FA739856F3820C503E9805EC /* XuqmPushSDK in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FF03A26413FA013A73FE6A74 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + E531EC5A0DDB7F4AC115B7B3 /* XuqmCoreSDK in Frameworks */, + 16B20ECD6495EB91694A2BD1 /* XuqmWebViewSDK in Frameworks */, + F594A24125851013F8EFC1CF /* XuqmUpdateSDK in Frameworks */, + E512D7F10071CFB4C5807BAD /* XuqmBugCollectSDK in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 221ACA908A8B0CA2009F888F /* xuqm */ = { + isa = PBXGroup; + children = ( + D6D139462BF98DD4383C3A2F /* 临床知识库.xuqmconfig */, + ); + path = xuqm; + sourceTree = ""; + }; + 46833927093A1737E932D833 /* xuqm */ = { + isa = PBXGroup; + children = ( + A6878AFA85D5F976AD1CCCEA /* 识校宝.xuqmconfig */, + ); + path = xuqm; + sourceTree = ""; + }; + 4A4FCF14A99376D99C709510 /* Clinical */ = { + isa = PBXGroup; + children = ( + 221ACA908A8B0CA2009F888F /* xuqm */, + 9424D4F36BD03EBEB32DD1BE /* AppConfig.swift */, + 8E26B616F9150DC3C4471F86 /* ClinicalApp.swift */, + BE4652CBDE3972A23969FF69 /* Info.plist */, + ); + path = Clinical; + sourceTree = ""; + }; + 58BB60E84F00A5C42888A700 /* Shared */ = { + isa = PBXGroup; + children = ( + 35BAEDD568654DD797878F59 /* AppHomeView.swift */, + 3CCC2FCA7B6919C2158639A1 /* DevUrlSettings.swift */, + BD20ED616BC8974D67BB37AB /* WebMessageHandler.swift */, + ); + path = Shared; + sourceTree = ""; + }; + 6EFE9EA0EC765F4CEF4220C0 /* Shijiaobao */ = { + isa = PBXGroup; + children = ( + 46833927093A1737E932D833 /* xuqm */, + 3DBA1791BE866D08A7349178 /* AppConfig.swift */, + 6E46F5314B429E382CA10453 /* Info.plist */, + 379EB60737598F6B2416A75D /* ShijiaobaoApp.swift */, + ); + path = Shijiaobao; + sourceTree = ""; + }; + 7447F0CFC2CAA759C81E1875 /* Products */ = { + isa = PBXGroup; + children = ( + 3CBEF42AAD17F5096C7D15A5 /* Clinical.app */, + 5430EE446D97FF9C6E9D260C /* Shijiaobao.app */, + 6B5DFE7FA7D60F730A58B71B /* Yiwangxin.app */, + ); + name = Products; + sourceTree = ""; + }; + 8ADA932EDB53A4668B44DEF6 /* Yiwangxin */ = { + isa = PBXGroup; + children = ( + CF76487C67772BE042E3A792 /* xuqm */, + 267FC0968185FCF9E8283018 /* AppConfig.swift */, + 9FAE13B6C156C664F7F944DB /* Info.plist */, + D2B6E7E7EB699C0C0EC012E3 /* PushNotificationHandler.swift */, + DC4F5611E16A156CE89BDC84 /* YiwangxinApp.swift */, + ); + path = Yiwangxin; + sourceTree = ""; + }; + CF76487C67772BE042E3A792 /* xuqm */ = { + isa = PBXGroup; + children = ( + C8E1DBEA3AAED25B6E99516B /* 医网信.xuqmconfig */, + ); + path = xuqm; + sourceTree = ""; + }; + D640D2E069DF56968A63A6B6 = { + isa = PBXGroup; + children = ( + 4A4FCF14A99376D99C709510 /* Clinical */, + 58BB60E84F00A5C42888A700 /* Shared */, + 6EFE9EA0EC765F4CEF4220C0 /* Shijiaobao */, + 8ADA932EDB53A4668B44DEF6 /* Yiwangxin */, + 7447F0CFC2CAA759C81E1875 /* Products */, + ); + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 37284F1797E0A23B27C274BB /* Clinical */ = { + isa = PBXNativeTarget; + buildConfigurationList = D88AD01EAE2091CCE0B5D8FB /* Build configuration list for PBXNativeTarget "Clinical" */; + buildPhases = ( + 4C9908F9CF0F4C27D9D8F6AC /* Sources */, + 8E0B88A9C0977C153C627FBA /* Resources */, + 19375A6390BDBFC6185E21EC /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Clinical; + packageProductDependencies = ( + 73D91AAAD0801E58CCBE0122 /* XuqmCoreSDK */, + 410DC73E7A3A6C59E0270F87 /* XuqmWebViewSDK */, + 3970C3CDC46BF50BBB1C661D /* XuqmUpdateSDK */, + F02302DCF2AB48FD75CA6050 /* XuqmBugCollectSDK */, + ); + productName = Clinical; + productReference = 3CBEF42AAD17F5096C7D15A5 /* Clinical.app */; + productType = "com.apple.product-type.application"; + }; + D25EE29EC808B75EEEEB24C2 /* Yiwangxin */ = { + isa = PBXNativeTarget; + buildConfigurationList = A8C7D71D0D5D33323798FF71 /* Build configuration list for PBXNativeTarget "Yiwangxin" */; + buildPhases = ( + 9B542F9D0E78B806D9094FEB /* Sources */, + A51BD5295D7AE18119D249A6 /* Resources */, + A06FF5C43835077B8F1868B8 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Yiwangxin; + packageProductDependencies = ( + 89FA3FE5B67B797F890612E0 /* XuqmCoreSDK */, + 63206297B881509DB98EC62D /* XuqmWebViewSDK */, + 80FE5716BCB8DE96E9FA44F3 /* XuqmUpdateSDK */, + B83686FDA7C16BCD559EF954 /* XuqmBugCollectSDK */, + 683C876CC716649F3640F88E /* XuqmPushSDK */, + ); + productName = Yiwangxin; + productReference = 6B5DFE7FA7D60F730A58B71B /* Yiwangxin.app */; + productType = "com.apple.product-type.application"; + }; + D76E1F28AC2725D5F9F43A52 /* Shijiaobao */ = { + isa = PBXNativeTarget; + buildConfigurationList = 209E77AC5D96814656D555E1 /* Build configuration list for PBXNativeTarget "Shijiaobao" */; + buildPhases = ( + 55C4DB32F52F59B9FAF86A24 /* Sources */, + F1B07A56B4BAB9DFBFF6353D /* Resources */, + FF03A26413FA013A73FE6A74 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Shijiaobao; + packageProductDependencies = ( + B988767C8936E6E2FD97F9F7 /* XuqmCoreSDK */, + 06B237FA7AA8B805CBB8CE5C /* XuqmWebViewSDK */, + DD1BD16A1DAFCCCF600D4965 /* XuqmUpdateSDK */, + 6425A5C3C87BE6A40511961F /* XuqmBugCollectSDK */, + ); + productName = Shijiaobao; + productReference = 5430EE446D97FF9C6E9D260C /* Shijiaobao.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 8BF19529BEF24B3D82EC576E /* Project object */ = { + isa = PBXProject; + attributes = { + BuildIndependentTargetsInParallel = YES; + LastUpgradeCheck = 1430; + TargetAttributes = { + }; + }; + buildConfigurationList = 48FD139CE322892873F56768 /* Build configuration list for PBXProject "YwxMobileApp" */; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + Base, + en, + ); + mainGroup = D640D2E069DF56968A63A6B6; + minimizedProjectReferenceProxies = 1; + packageReferences = ( + BA8F38795CC0A8A348A36187 /* XCRemoteSwiftPackageReference "XuqmGroup-iOSSDK" */, + ); + preferredProjectObjectVersion = 77; + productRefGroup = 7447F0CFC2CAA759C81E1875 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 37284F1797E0A23B27C274BB /* Clinical */, + D76E1F28AC2725D5F9F43A52 /* Shijiaobao */, + D25EE29EC808B75EEEEB24C2 /* Yiwangxin */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8E0B88A9C0977C153C627FBA /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A6125511E046A1E7BAC9E768 /* 临床知识库.xuqmconfig in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A51BD5295D7AE18119D249A6 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 46B250AEADA8EC95943857C3 /* 医网信.xuqmconfig in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + F1B07A56B4BAB9DFBFF6353D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 090E9A24EB09B39C7A10EBF8 /* 识校宝.xuqmconfig in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 4C9908F9CF0F4C27D9D8F6AC /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + AEA5E916B8FDEE608E8B2924 /* AppConfig.swift in Sources */, + F8824380AE3739CF807A378E /* AppHomeView.swift in Sources */, + 4FBCDF6B8B8B1985C5274D86 /* ClinicalApp.swift in Sources */, + 7262F4ABAB6A240FBAF64307 /* DevUrlSettings.swift in Sources */, + A025F926434AAFEBE9713898 /* WebMessageHandler.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 55C4DB32F52F59B9FAF86A24 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + E8DCFDB7174976BBD889E0A0 /* AppConfig.swift in Sources */, + B13A671EADB8C936B2C8D585 /* AppHomeView.swift in Sources */, + DF14B714F9B3BD4338080FC3 /* DevUrlSettings.swift in Sources */, + FCAC187B65C48424E1492469 /* ShijiaobaoApp.swift in Sources */, + 07930E6D06F64D799554D770 /* WebMessageHandler.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 9B542F9D0E78B806D9094FEB /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 14222A448C4408CCEBE61891 /* AppConfig.swift in Sources */, + FE071409934D229E6BBA9D4C /* AppHomeView.swift in Sources */, + 1E7A88E490CDFDEB0A3188FB /* DevUrlSettings.swift in Sources */, + 1BC190112E6A65B141D54675 /* PushNotificationHandler.swift in Sources */, + 048663940A87F400EAF53D8F /* WebMessageHandler.swift in Sources */, + C449D9323499A54C82267545 /* YiwangxinApp.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + 334112FA1B8FDACBB54E4C96 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "DEBUG=1", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MARKETING_VERSION = 1.0.0; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.9; + }; + name = Debug; + }; + 3B8A80A379A23B196BE5C9B9 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 16.0; + MARKETING_VERSION = 1.0.0; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_VERSION = 5.9; + }; + name = Release; + }; + 57F0EDCE7D38DDD6E74F67E1 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + INFOPLIST_FILE = Yiwangxin/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = cn.org.bjca.wcert.ywq.ios; + PRODUCT_NAME = Yiwangxin; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = PUSH_ENABLED; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + A3CF7AD77DFC96573F9DE96A /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + INFOPLIST_FILE = Clinical/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.szyx.app.clinical; + PRODUCT_NAME = Clinical; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + C302AAA4F72AA0D672CF5A97 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + INFOPLIST_FILE = Shijiaobao/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.szyx.app.shijiaobao; + PRODUCT_NAME = Shijiaobao; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + C6B2DA29168963992BC587E6 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + INFOPLIST_FILE = Shijiaobao/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.szyx.app.shijiaobao; + PRODUCT_NAME = Shijiaobao; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + C863BF9A78B81AA2EA7C0854 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + INFOPLIST_FILE = Clinical/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.szyx.app.clinical; + PRODUCT_NAME = Clinical; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; + DED56984F8B3902C2F8D0CAF /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + INFOPLIST_FILE = Yiwangxin/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = cn.org.bjca.wcert.ywq.ios; + PRODUCT_NAME = Yiwangxin; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG PUSH_ENABLED"; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 209E77AC5D96814656D555E1 /* Build configuration list for PBXNativeTarget "Shijiaobao" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + C6B2DA29168963992BC587E6 /* Debug */, + C302AAA4F72AA0D672CF5A97 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + 48FD139CE322892873F56768 /* Build configuration list for PBXProject "YwxMobileApp" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 334112FA1B8FDACBB54E4C96 /* Debug */, + 3B8A80A379A23B196BE5C9B9 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + A8C7D71D0D5D33323798FF71 /* Build configuration list for PBXNativeTarget "Yiwangxin" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + DED56984F8B3902C2F8D0CAF /* Debug */, + 57F0EDCE7D38DDD6E74F67E1 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + D88AD01EAE2091CCE0B5D8FB /* Build configuration list for PBXNativeTarget "Clinical" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A3CF7AD77DFC96573F9DE96A /* Debug */, + C863BF9A78B81AA2EA7C0854 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + +/* Begin XCRemoteSwiftPackageReference section */ + BA8F38795CC0A8A348A36187 /* XCRemoteSwiftPackageReference "XuqmGroup-iOSSDK" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://xuqinmin.com/xuqmGroup/XuqmGroup-iOSSDK.git"; + requirement = { + branch = main; + kind = branch; + }; + }; +/* End XCRemoteSwiftPackageReference section */ + +/* Begin XCSwiftPackageProductDependency section */ + 06B237FA7AA8B805CBB8CE5C /* XuqmWebViewSDK */ = { + isa = XCSwiftPackageProductDependency; + package = BA8F38795CC0A8A348A36187 /* XCRemoteSwiftPackageReference "XuqmGroup-iOSSDK" */; + productName = XuqmWebViewSDK; + }; + 3970C3CDC46BF50BBB1C661D /* XuqmUpdateSDK */ = { + isa = XCSwiftPackageProductDependency; + package = BA8F38795CC0A8A348A36187 /* XCRemoteSwiftPackageReference "XuqmGroup-iOSSDK" */; + productName = XuqmUpdateSDK; + }; + 410DC73E7A3A6C59E0270F87 /* XuqmWebViewSDK */ = { + isa = XCSwiftPackageProductDependency; + package = BA8F38795CC0A8A348A36187 /* XCRemoteSwiftPackageReference "XuqmGroup-iOSSDK" */; + productName = XuqmWebViewSDK; + }; + 63206297B881509DB98EC62D /* XuqmWebViewSDK */ = { + isa = XCSwiftPackageProductDependency; + package = BA8F38795CC0A8A348A36187 /* XCRemoteSwiftPackageReference "XuqmGroup-iOSSDK" */; + productName = XuqmWebViewSDK; + }; + 6425A5C3C87BE6A40511961F /* XuqmBugCollectSDK */ = { + isa = XCSwiftPackageProductDependency; + package = BA8F38795CC0A8A348A36187 /* XCRemoteSwiftPackageReference "XuqmGroup-iOSSDK" */; + productName = XuqmBugCollectSDK; + }; + 683C876CC716649F3640F88E /* XuqmPushSDK */ = { + isa = XCSwiftPackageProductDependency; + package = BA8F38795CC0A8A348A36187 /* XCRemoteSwiftPackageReference "XuqmGroup-iOSSDK" */; + productName = XuqmPushSDK; + }; + 73D91AAAD0801E58CCBE0122 /* XuqmCoreSDK */ = { + isa = XCSwiftPackageProductDependency; + package = BA8F38795CC0A8A348A36187 /* XCRemoteSwiftPackageReference "XuqmGroup-iOSSDK" */; + productName = XuqmCoreSDK; + }; + 80FE5716BCB8DE96E9FA44F3 /* XuqmUpdateSDK */ = { + isa = XCSwiftPackageProductDependency; + package = BA8F38795CC0A8A348A36187 /* XCRemoteSwiftPackageReference "XuqmGroup-iOSSDK" */; + productName = XuqmUpdateSDK; + }; + 89FA3FE5B67B797F890612E0 /* XuqmCoreSDK */ = { + isa = XCSwiftPackageProductDependency; + package = BA8F38795CC0A8A348A36187 /* XCRemoteSwiftPackageReference "XuqmGroup-iOSSDK" */; + productName = XuqmCoreSDK; + }; + B83686FDA7C16BCD559EF954 /* XuqmBugCollectSDK */ = { + isa = XCSwiftPackageProductDependency; + package = BA8F38795CC0A8A348A36187 /* XCRemoteSwiftPackageReference "XuqmGroup-iOSSDK" */; + productName = XuqmBugCollectSDK; + }; + B988767C8936E6E2FD97F9F7 /* XuqmCoreSDK */ = { + isa = XCSwiftPackageProductDependency; + package = BA8F38795CC0A8A348A36187 /* XCRemoteSwiftPackageReference "XuqmGroup-iOSSDK" */; + productName = XuqmCoreSDK; + }; + DD1BD16A1DAFCCCF600D4965 /* XuqmUpdateSDK */ = { + isa = XCSwiftPackageProductDependency; + package = BA8F38795CC0A8A348A36187 /* XCRemoteSwiftPackageReference "XuqmGroup-iOSSDK" */; + productName = XuqmUpdateSDK; + }; + F02302DCF2AB48FD75CA6050 /* XuqmBugCollectSDK */ = { + isa = XCSwiftPackageProductDependency; + package = BA8F38795CC0A8A348A36187 /* XCRemoteSwiftPackageReference "XuqmGroup-iOSSDK" */; + productName = XuqmBugCollectSDK; + }; +/* End XCSwiftPackageProductDependency section */ + }; + rootObject = 8BF19529BEF24B3D82EC576E /* Project object */; +} diff --git a/YwxMobileApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/YwxMobileApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..919434a --- /dev/null +++ b/YwxMobileApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/project.yml b/project.yml new file mode 100644 index 0000000..c317396 --- /dev/null +++ b/project.yml @@ -0,0 +1,165 @@ +name: YwxMobileApp +options: + deploymentTarget: + iOS: "16.0" + createIntermediateGroups: true + groupSortPosition: top + defaultConfig: Debug + +packages: + # SNAPSHOT 开发期使用 branch: main(Jenkins SNAPSHOT job 推送到此分支) + # 正式发版后改为 from: "x.y.z"(对应 Jenkins Release job 打出的 tag) + # 本地 SDK 调试可临时改为 path: ../../../XuqmGroup/XuqmGroup-iOSSDK + XuqmGroupSDK: + url: https://xuqinmin.com/xuqmGroup/XuqmGroup-iOSSDK.git + branch: main + +settings: + base: + SWIFT_VERSION: "5.9" + IPHONEOS_DEPLOYMENT_TARGET: "16.0" + MARKETING_VERSION: "1.0.0" + CURRENT_PROJECT_VERSION: "1" + DEBUG_INFORMATION_FORMAT: dwarf-with-dsym + ENABLE_HARDENED_RUNTIME: YES + +configs: + Debug: debug + Release: release + +targets: + + # ─────────────────── Shijiaobao ─────────────────── + Shijiaobao: + type: application + platform: iOS + sources: + - path: Shared + - path: Shijiaobao + info: + path: Shijiaobao/Info.plist + properties: + CFBundleDisplayName: 识校宝 + CFBundleShortVersionString: "$(MARKETING_VERSION)" + CFBundleVersion: "$(CURRENT_PROJECT_VERSION)" + UILaunchScreen: + UILaunchScreen: {} + NSAppTransportSecurity: + NSAllowsArbitraryLoads: true + UISupportedInterfaceOrientations: + - UIInterfaceOrientationPortrait + - UIInterfaceOrientationPortraitUpsideDown + - UIInterfaceOrientationLandscapeLeft + - UIInterfaceOrientationLandscapeRight + settings: + base: + PRODUCT_BUNDLE_IDENTIFIER: com.szyx.app.shijiaobao + PRODUCT_NAME: Shijiaobao + ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon + configs: + Debug: + SWIFT_ACTIVE_COMPILATION_CONDITIONS: DEBUG + resources: + - path: Shijiaobao/xuqm + type: folder + dependencies: + - package: XuqmGroupSDK + product: XuqmCoreSDK + - package: XuqmGroupSDK + product: XuqmWebViewSDK + - package: XuqmGroupSDK + product: XuqmUpdateSDK + - package: XuqmGroupSDK + product: XuqmBugCollectSDK + + # ─────────────────── Clinical ─────────────────── + Clinical: + type: application + platform: iOS + sources: + - path: Shared + - path: Clinical + info: + path: Clinical/Info.plist + properties: + CFBundleDisplayName: 临床知识库 + CFBundleShortVersionString: "$(MARKETING_VERSION)" + CFBundleVersion: "$(CURRENT_PROJECT_VERSION)" + UILaunchScreen: + UILaunchScreen: {} + NSAppTransportSecurity: + NSAllowsArbitraryLoads: true + UISupportedInterfaceOrientations: + - UIInterfaceOrientationPortrait + - UIInterfaceOrientationPortraitUpsideDown + - UIInterfaceOrientationLandscapeLeft + - UIInterfaceOrientationLandscapeRight + settings: + base: + PRODUCT_BUNDLE_IDENTIFIER: com.szyx.app.clinical + PRODUCT_NAME: Clinical + ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon + configs: + Debug: + SWIFT_ACTIVE_COMPILATION_CONDITIONS: DEBUG + resources: + - path: Clinical/xuqm + type: folder + dependencies: + - package: XuqmGroupSDK + product: XuqmCoreSDK + - package: XuqmGroupSDK + product: XuqmWebViewSDK + - package: XuqmGroupSDK + product: XuqmUpdateSDK + - package: XuqmGroupSDK + product: XuqmBugCollectSDK + + # ─────────────────── Yiwangxin ─────────────────── + Yiwangxin: + type: application + platform: iOS + sources: + - path: Shared + - path: Yiwangxin + info: + path: Yiwangxin/Info.plist + properties: + CFBundleDisplayName: 医网信 + CFBundleShortVersionString: "$(MARKETING_VERSION)" + CFBundleVersion: "$(CURRENT_PROJECT_VERSION)" + UILaunchScreen: + UILaunchScreen: {} + NSAppTransportSecurity: + NSAllowsArbitraryLoads: true + UIBackgroundModes: + - remote-notification + UISupportedInterfaceOrientations: + - UIInterfaceOrientationPortrait + - UIInterfaceOrientationPortraitUpsideDown + - UIInterfaceOrientationLandscapeLeft + - UIInterfaceOrientationLandscapeRight + settings: + base: + PRODUCT_BUNDLE_IDENTIFIER: cn.org.bjca.wcert.ywq.ios + PRODUCT_NAME: Yiwangxin + ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon + configs: + Debug: + SWIFT_ACTIVE_COMPILATION_CONDITIONS: "DEBUG PUSH_ENABLED" + Release: + SWIFT_ACTIVE_COMPILATION_CONDITIONS: PUSH_ENABLED + resources: + - path: Yiwangxin/xuqm + type: folder + dependencies: + - package: XuqmGroupSDK + product: XuqmCoreSDK + - package: XuqmGroupSDK + product: XuqmWebViewSDK + - package: XuqmGroupSDK + product: XuqmUpdateSDK + - package: XuqmGroupSDK + product: XuqmBugCollectSDK + - package: XuqmGroupSDK + product: XuqmPushSDK diff --git a/scripts/use-sdk.sh b/scripts/use-sdk.sh new file mode 100755 index 0000000..dd53a39 --- /dev/null +++ b/scripts/use-sdk.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash +# 切换 iOS SDK 引用方式,更新 sdk.properties 和 project.yml,然后重新生成 Xcode 项目 +# +# 用法: +# ./scripts/use-sdk.sh main # SNAPSHOT: 使用 main 分支(最新代码) +# ./scripts/use-sdk.sh 1.0.2 # Release: 使用指定 tag +# ./scripts/use-sdk.sh local # 本地调试: 使用本地 SDK 源码路径 +# +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$(dirname "$SCRIPT_DIR")" +GITEA_URL="https://xuqinmin.com/xuqmGroup/XuqmGroup-iOSSDK.git" +LOCAL_PATH="../../../XuqmGroup/XuqmGroup-iOSSDK" + +usage() { + echo "Usage: $0 " + echo " main — SNAPSHOT,引用 main 分支(开发期默认)" + echo " local — 本地 SDK 源码路径(SDK 开发调试,不要提交此状态)" + echo " x.y.z — 正式版本 tag(如 1.0.2)" + exit 1 +} + +[[ $# -eq 1 ]] || usage +REF="$1" + +cd "$PROJECT_DIR" + +# ── 更新 sdk.properties ─────────────────────────────────────────────────────── +sed -i '' "s/^SDK_REF=.*/SDK_REF=${REF}/" sdk.properties +echo "[use-sdk] sdk.properties → SDK_REF=${REF}" + +# ── 生成 project.yml 的包引用块 ─────────────────────────────────────────────── +if [[ "$REF" == "local" ]]; then + PKG_BLOCK="packages:\n XuqmGroupSDK:\n path: ${LOCAL_PATH}" + echo "[use-sdk] 模式: 本地路径 (${LOCAL_PATH})" +elif [[ "$REF" == "main" ]]; then + PKG_BLOCK="packages:\n XuqmGroupSDK:\n url: ${GITEA_URL}\n branch: main" + echo "[use-sdk] 模式: SNAPSHOT (branch: main)" +else + PKG_BLOCK="packages:\n XuqmGroupSDK:\n url: ${GITEA_URL}\n from: \"${REF}\"" + echo "[use-sdk] 模式: Release (from: ${REF})" +fi + +# ── 替换 project.yml 中的 packages 块 ──────────────────────────────────────── +# 用 Python 替换 packages: 到下一个顶层 key 之间的内容 +python3 - "$PKG_BLOCK" <<'PYEOF' +import sys, re + +pkg_block = sys.argv[1] +with open('project.yml', 'r') as f: + content = f.read() + +# Replace from 'packages:' up to (but not including) the next top-level key +new_content = re.sub( + r'^packages:.*?(?=^\w)', + pkg_block.replace('\\n', '\n') + '\n\n', + content, + flags=re.MULTILINE | re.DOTALL +) +with open('project.yml', 'w') as f: + f.write(new_content) +print("[use-sdk] project.yml 已更新") +PYEOF + +# ── 重新生成 Xcode 项目 ─────────────────────────────────────────────────────── +if command -v xcodegen &>/dev/null; then + xcodegen generate + echo "[use-sdk] xcodegen generate 完成" +else + echo "[use-sdk] 警告: xcodegen 未安装,请手动运行 xcodegen generate" +fi diff --git a/sdk.properties b/sdk.properties new file mode 100644 index 0000000..302e19a --- /dev/null +++ b/sdk.properties @@ -0,0 +1,16 @@ +# iOS SDK 版本引用 +# 对应 Android libs.versions.toml 中的 SDK 版本管理 +# +# SNAPSHOT 开发期: +# SDK_REF=main +# project.yml -> branch: "main" +# +# 正式发版时(由开发者或 Jenkins 更新): +# SDK_REF=1.0.2 +# project.yml -> from: "1.0.2" +# +# 本地 SDK 开发调试(临时,不提交): +# SDK_REF=local +# project.yml -> path: ../../../XuqmGroup/XuqmGroup-iOSSDK +# +SDK_REF=main