{"id":"90f97eb6-5720-4de5-8042-6b2606a8f57e","entityType":"agent","slug":"clawhub-golgys0621-foxmobile-dev-doc","name":"狐表(Foxtable) 移动端开发","canonicalUrl":"https://www.xpersona.co/agent/clawhub-golgys0621-foxmobile-dev-doc","canonicalPath":"/agent/clawhub-golgys0621-foxmobile-dev-doc","generatedAt":"2026-10-11T22:50:20.350Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-10-11T19:27:56.617Z","emptyReason":null},"description":"狐表(Foxtable)移动端开发完整文档，涵盖HTML入门、WeUI框架、微信接口、客户端类、JSON解析、异步编程、Web数据源等，完整保留多层导航结构 Skill: 狐表(Foxtable) 移动端开发 Owner: golgys0621 Summary: 狐表(Foxtable)移动端开发完整文档，涵盖HTML入门、WeUI框架、微信接口、客户端类、JSON解析、异步编程、Web数据源等，完整保留多层导航结构 Tags: HTML:2.0.5, WeUI:2.0.5, foxtable:2.0.5, latest:2.0.5, weui:2.0.3 Version history: v2.0.5 | 2026-07-21T08:17:21.204Z | user v2.0.5: 修正显示名称，统一为「狐表(Foxtable)」品牌前缀 v2.0.4 | 2026-07-21T08:09:31.907Z | user v2.0.4: 统一命名为「狐表(Foxtable)」品牌前缀 v2.0.3 | 2026-07-21T02:49:22.965Z | user 提供规范 ski","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1K downloads reported by the source. Last updated 10/11/2026.","installCommand":"clawhub skill install s17cp6mteg7mk9rg41sbqbftyh8a9x4t:foxmobile-dev-doc","sourceUrl":"https://clawhub.ai/golgys0621/foxmobile-dev-doc","homepage":"https://clawhub.ai/golgys0621/skills/foxmobile-dev-doc","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/golgys0621/foxmobile-dev-doc","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/golgys0621/skills/foxmobile-dev-doc","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":60,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"狐表(Foxtable)移动端开发完整文档，涵盖HTML入门、WeUI框架、微信接口、客户端类、JSON解析、异步编程、Web数据源等，完整保留多层导航结构 Skill: 狐表(Foxtable) 移动端开发 Owner: golgys0621 Summary: 狐表(Foxtable)移动端开发完整文档，涵盖HTML"},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-11T19:27:56.617Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T19:27:56.617Z","emptyReason":null},"stars":null,"forks":null,"downloads":1003,"likes":null,"task":null,"library":null,"packageName":null,"latestVersion":"2.0.5","tractionLabel":"1K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-11T19:27:56.602Z","emptyReason":null},"lastUpdatedAt":"2026-10-11T19:27:56.617Z","lastCrawledAt":"2026-10-11T19:27:56.602Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-12T19:27:56.602Z","lastVerifiedAt":null,"highlights":[{"version":"2.0.5","createdAt":"2026-07-21T08:17:21.204Z","changelog":"v2.0.5: 修正显示名称，统一为「狐表(Foxtable)」品牌前缀","fileCount":20,"zipByteSize":216423},{"version":"2.0.4","createdAt":"2026-07-21T08:09:31.907Z","changelog":"v2.0.4: 统一命名为「狐表(Foxtable)」品牌前缀","fileCount":20,"zipByteSize":216353},{"version":"2.0.3","createdAt":"2026-07-21T02:49:22.965Z","changelog":"提供规范 skill-card.md 修复 card.missing；slug 沿用原有覆盖升级","fileCount":20,"zipByteSize":216484},{"version":"2.0.2","createdAt":"2026-07-21T02:43:20.270Z","changelog":"标准化 SKILL.md 前端为 name/description，ClawHub 自动生成 skill-card.md，修复 card.missing；slug 沿用原有覆盖升级","fileCount":20,"zipByteSize":216322},{"version":"2.0.1","createdAt":"2026-07-21T02:38:10.701Z","changelog":"补充 ClawHub 强制要求的 skill-card.md 安全卡片，修复 card.missing 验证失败；slug 沿用原有覆盖升级","fileCount":20,"zipByteSize":216364},{"version":"2.0.0","createdAt":"2026-07-21T01:49:59.109Z","changelog":"基于最新 foxmobile.chm 完整重转：17 章、完整保留多层导航","fileCount":20,"zipByteSize":216409},{"version":"1.1.0","createdAt":"2026-07-13T02:18:47.121Z","changelog":"v1.1.0: 完整保留多层导航结构(foxtable 9层/foxmobile 5层)，新增遗漏页面，标题层级映射原始CHM导航树","fileCount":20,"zipByteSize":214673},{"version":"1.0.0","createdAt":"2026-07-10T11:18:48.719Z","changelog":"首版发布：从 foxmobile.chm 转换，涵盖HTML入门、WeUI框架、微信接口等17章294页","fileCount":20,"zipByteSize":214951}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s17cp6mteg7mk9rg41sbqbftyh8a9x4t:foxmobile-dev-doc","setupComplexity":"low","setupSteps":["Setup complexity is classified as HIGH. You must provision dedicated cloud infrastructure or an isolated VM. Do not run this directly on your local workstation.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-golgys0621-foxmobile-dev-doc/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-golgys0621-foxmobile-dev-doc/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-golgys0621-foxmobile-dev-doc/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-golgys0621-foxmobile-dev-doc/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-golgys0621-foxmobile-dev-doc/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-golgys0621-foxmobile-dev-doc/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-11T22:50:20.346Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-golgys0621-foxmobile-dev-doc/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-golgys0621-foxmobile-dev-doc/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-golgys0621-foxmobile-dev-doc/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-golgys0621-foxmobile-dev-doc/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"high","updatedAt":"2026-10-11T19:27:56.617Z","emptyReason":null},"readme":"Skill: 狐表(Foxtable) 移动端开发\n\nOwner: golgys0621\n\nSummary: 狐表(Foxtable)移动端开发完整文档，涵盖HTML入门、WeUI框架、微信接口、客户端类、JSON解析、异步编程、Web数据源等，完整保留多层导航结构\n\nTags: HTML:2.0.5, WeUI:2.0.5, foxtable:2.0.5, latest:2.0.5, weui:2.0.3\n\nVersion history:\n\nv2.0.5 | 2026-07-21T08:17:21.204Z | user\n\nv2.0.5: 修正显示名称，统一为「狐表(Foxtable)」品牌前缀\n\nv2.0.4 | 2026-07-21T08:09:31.907Z | user\n\nv2.0.4: 统一命名为「狐表(Foxtable)」品牌前缀\n\nv2.0.3 | 2026-07-21T02:49:22.965Z | user\n\n提供规范 skill-card.md 修复 card.missing；slug 沿用原有覆盖升级\n\nv2.0.2 | 2026-07-21T02:43:20.270Z | user\n\n标准化 SKILL.md 前端为 name/description，ClawHub 自动生成 skill-card.md，修复 card.missing；slug 沿用原有覆盖升级\n\nv2.0.1 | 2026-07-21T02:38:10.701Z | user\n\n补充 ClawHub 强制要求的 skill-card.md 安全卡片，修复 card.missing 验证失败；slug 沿用原有覆盖升级\n\nv2.0.0 | 2026-07-21T01:49:59.109Z | user\n\n基于最新 foxmobile.chm 完整重转：17 章、完整保留多层导航\n\nv1.1.0 | 2026-07-13T02:18:47.121Z | user\n\nv1.1.0: 完整保留多层导航结构(foxtable 9层/foxmobile 5层)，新增遗漏页面，标题层级映射原始CHM导航树\n\nv1.0.0 | 2026-07-10T11:18:48.719Z | user\n\n首版发布：从 foxmobile.chm 转换，涵盖HTML入门、WeUI框架、微信接口等17章294页\n\nArchive index:\n\nArchive v2.0.5: 20 files, 216423 bytes\n\nFiles: references/00_关于文档构成.md (2632b), references/01_关于移动开发.md (1112b), references/02_关于功能演示.md (492b), references/03_关于网页设计.md (542b), references/04_HTML入门.md (120974b), references/05_WeUI框架.md (320984b), references/06_扩展WeUI的功能.md (339b), references/07_客户端类.md (27069b), references/08_JSON解析.md (26750b), references/09_微信接口.md (278113b), references/10_更多接口.md (2978b), references/11_生成并发送PDF文件.md (2186b), references/12_生成并发送Word文件.md (1481b), references/13_用Excel报表生成网页.md (27468b), references/14_类型参考.md (19626b), references/15_异步编程.md (37254b), references/16_Web数据源.md (8037b), skill-card.md (3052b), SKILL.md (3339b), _meta.json (136b)\n\nFile v2.0.5:SKILL.md\n\n---\r\nname: \"狐表(Foxtable) 移动端开发\"\r\ndescription: \"狐表(Foxtable)移动端开发完整文档，涵盖HTML入门、WeUI框架、微信接口、客户端类、JSON解析、异步编程、Web数据源等，完整保留多层导航结构\"\r\nread_when:\r\n  - foxmobile\r\n  - foxtable 移动开发\r\n  - foxtable 手机\r\n  - foxtable 微信接口\r\n  - foxtable WeUI\r\n  - foxtable web开发\r\n  - foxtable HTML\r\n  - foxtable 客户端类\r\n  - foxtable JSON\r\n  - foxtable 异步编程\r\n  - foxtable web数据源\r\n  - 狐表 移动\r\n---\r\n\r\n# Foxtable 移动开发文档\r\n\r\n本技能包含狐表(Foxtable)移动端开发的完整帮助文档，从 CHM 文件转换而来，**完整保留了原始的多层导航结构（最深 5 层）**。Foxtable 移动开发用于基于 Foxtable 构建手机端管理系统，支持 B/S 架构的 Web 应用开发。\r\n\r\n## 何时使用\r\n\r\n当用户询问 Foxtable/狐表 的移动开发、Web开发、微信接口、WeUI框架、HTML页面开发、客户端类、JSON解析、异步编程、Web数据源等问题时，加载对应的参考文档进行回答。\r\n\r\n## 参考文档索引\r\n\r\n按章节组织，每个文件包含该章节下的全部页面内容（文件内用 `#`/`##`/`###`/`####`/`#####` 多层标题还原导航层级，可精确定位）：\r\n\r\n| 文件 | 章节 | 页面数 | 内容概要 |\r\n|------|------|--------|----------|\r\n| `references/00_关于文档构成.md` | 关于文档构成 | 1 | 文档结构说明 |\r\n| `references/01_关于移动开发.md` | 关于移动开发 | 1 | 移动开发概述 |\r\n| `references/02_关于功能演示.md` | 关于功能演示 | 1 | 功能演示说明 |\r\n| `references/03_关于网页设计.md` | 关于网页设计 | 1 | 网页设计原则 |\r\n| `references/04_HTML入门.md` | HTML入门 | 46 | HTML基础、服务器配置、Hello World |\r\n| `references/05_WeUI框架.md` | WeUI框架 | 109 | WeUI组件、样式、布局（核心章节） |\r\n| `references/06_扩展WeUI的功能.md` | 扩展WeUI | 1 | WeUI功能扩展 |\r\n| `references/07_客户端类.md` | 客户端类 | 14 | 客户端API与操作 |\r\n| `references/08_JSON解析.md` | JSON解析 | 9 | JSON数据处理 |\r\n| `references/09_微信接口.md` | 微信接口 | 76 | 微信公众号、支付、模板消息等（核心章节） |\r\n| `references/10_更多接口.md` | 更多接口 | 1 | 其他API接口 |\r\n| `references/11_生成并发送PDF文件.md` | PDF文件 | 1 | PDF生成与发送 |\r\n| `references/12_生成并发送Word文件.md` | Word文件 | 1 | Word生成与发送 |\r\n| `references/13_用Excel报表生成网页.md` | Excel报表网页 | 6 | Excel报表转网页 |\r\n| `references/14_类型参考.md` | 类型参考 | 10 | 数据类型速查 |\r\n| `references/15_异步编程.md` | 异步编程 | 14 | 异步操作与回调 |\r\n| `references/16_Web数据源.md` | Web数据源 | 3 | Web端数据源连接 |\r\n\r\n## 使用方式\r\n\r\n1. 根据用户问题判断属于哪个章节\r\n2. 读取对应的 references 文件\r\n3. 文件内按 `#`/`##`/`###` 等多层标题定位具体内容（导航层级已完整保留）\r\n4. WeUI框架(05)和微信接口(09)是两个核心章节\r\n\r\n## 文档来源\r\n\r\n原始文件: `E:\\foxtable\\Development\\foxmobile.chm` (6.6MB)\r\n转换时间: 2026-07-21\r\n原始页面数: 295 页 HTML\r\n导航层级: 最深 5 层，完整保留（无截断）\n\nFile v2.0.5:_meta.json\n\n{\n  \"ownerId\": \"kn77z6synmrxqwaha37s8r2b7h8a9k07\",\n  \"slug\": \"foxmobile-dev-doc\",\n  \"version\": \"2.0.5\",\n  \"publishedAt\": 1784621841204\n}\n\nFile v2.0.5:references/00_关于文档构成.md\n\n# 关于文档构成\r\n\r\n\r\n# 关于文档构成\r\n\r\n关于文档构成\r\n\r\n\r\n**关于文档构成**\r\n\r\n本文档是Foxtable的《高级开发指南》，是Foxtable主文档的补充，用于介绍部分Foxtable高级开发版特有的功能，例如网页设计入门、手机管理系统的开发，微信接入、三层架构设计和多线程编程等等。\r\n\r\n\r\n我们必须在已经掌握《使用指南》和《开发指南》的基础上，才能学习《高级开发指南》。\r\n\r\n本文档主要由七个部分组成\r\n\r\n* **HTML入门**\r\n  这一章介绍了如何开启一个HTTP服务，如果和用HTML生成网页，如何接收用户通过网页输入的数据，如何保存用户上传的文件等等。一般用户对于HTML稍作了解即可，因为实际开发的时候，我们一般会用内置的WeUI框架生成网页。\r\n* **WeUI框架**\r\n  这一张是重点，通过这一章，你会发现在Foxtable生成网页是多么的简单。\r\n  WeUI框架来自于腾讯，我们对其进行了一些扩展，增加了表格和AJAX功能，通过框架，我们可以用非常简单的代码生成专业效果的网页，本框架主要针对移动设备，虽然也能在PC上使用，但在手机上的效果更好。\r\n* **客户端类**\r\n\r\n  这一章介绍了客户端类HttpClient，Foxtable通过HttpClient和HTTP服务进行交互，包括第三方的HTTP服务。我们在进行微信开发的时候，实际上就是通过HttpClient调用腾讯提供的HTTP服务。\r\n* **JSON解析**\r\n\r\n  这一章介绍了JSON和XML数据的解析生成，互联网中的数据传递格式主要是JSON和XML，例如进行微信开发的时候，你会发现除了回调部分的数据是XML格式，其余都是JSON格式。\r\n  通过这一章，你会发现在Foxtable进行JSON和XML的解析与生成，实在是太轻松了。\r\n* **微信接口**\r\n\r\n  这一章介绍了如何在Foxtable进行微信开发，分别针对公众号和企业号进行讲述，基本上涵盖了所有微信常用接口，由于我们进行了适度的封装，微信开发变得前所未有的轻松。实际上不仅是微信开发，你可以用Foxtable完成任何第三方接口的开发，帮助就提供了一个阿里云快递查询接口的开发示例，只有短短的三行代码而已\r\n  。\r\n* **Web数据源**\r\n  Foxtable默认是两层结构，客户端直接连接数据库，这一章介绍如何使用Web数据源设计三层架构的系统，使得客户端不再直连数据库。\r\n* **异步编程**\r\n  这一节章介绍如何如何用异步编程同时处理多个任务，以提高服务器的负载能力。\n\nFile v2.0.5:references/01_关于移动开发.md\n\n# 关于移动开发\r\n\r\n\r\n# 关于移动开发\r\n\r\n关于移动开发\r\n\r\n\r\n**关于移动开发**\r\n\r\nFoxtable的高级开发版提供了移动开发功能。\r\n\r\nFoxtable的移动开发功能以B/S形式实现，内置手机网页自动生成功能，你不需要额外的学习，你可以完全不懂网页设计，也可以快速开发基于手机的企业管理系统，包括服务端的架设和网页的生成，全都可以在Foxtable中用最简单的方式完成，不仅简单到人人都能掌握，而且效率极高，相对于其它开发工具，代码节省了85%左右。\r\n\r\n此外高级开发版还提供了微信接入功能，用户可以直接通过微信输入、查询、统计和审核数据，还可以直接通过手机扫描二维码并将扫描结果发送到Foxtable。\r\n\r\n下面是Foxtable自动生成的一些手机网页。\r\n\r\n一个订单编辑页面：\r\n\r\n\r\n\r\n图片浏览和上传页面：\r\n\r\n\r\n\r\n输入界面很灵活：\r\n\r\n\r\n\r\n可以直接在手机端生成Excel报表：\r\n\r\n\r\n\r\n可以直接扫描条形码或者二维码输入数据：\r\n\r\n\r\n\r\n\r\n提供了多种输入组件：\n\nFile v2.0.5:references/02_关于功能演示.md\n\n# 关于功能演示\r\n\r\n\r\n# 关于功能演示\r\n\r\n关于功能演示\r\n\r\n\r\n**关于功能演示**\r\n\r\n你可以用微信扫描以下二维码，关注Foxtable的公众号：\r\n\r\n我们在这个公众号上做了一个简单的示例，进入公众号后，单击菜单“移动测试”可以进入这个示例。\r\n\r\n我们很难用一个例子演示完毕Foxtable的移动开发功能，只要你认真学习本文档，就能很轻松地用Foxtable设计出很专业的手机端管理系统。\n\nFile v2.0.5:references/03_关于网页设计.md\n\n# 关于网页设计\r\n\r\n\r\n# 关于网页设计\r\n\r\n关于网页设计\r\n\r\n\r\n**关于网页设计**\r\n\r\n在本文档的开始部分，我们会介绍一些网页设计方面的知识，让大家了解一下网页设计是怎么回事。\r\n\r\n实际上你可以完全不会网页设计，Foxtable内置了网页自动生成功能(通过扩展后的WeUI框架实现)，你不需要有任何的网页设计知识，一样可以设计出具备专业效果的网页来。\r\n\r\n例如你只需寥寥十来行代码，就可以设计出下面的网页来：\n\nFile v2.0.5:references/04_HTML入门.md\n\n# HTML入门\r\n\r\n\r\n## Hello World\r\n\r\nHello Word\r\n\r\n\r\n**Hello Word**\r\n\r\n我们先用一个经典的例子，来说明如何使用Foxtable实现http服务。\r\n\r\n首先是一个重要提示：要启动http服务，必须以管理员身份运行Foxtable。\r\n\r\n你可以在Windows的桌面上右击Foxtable的快捷方式，在快捷菜单中的单击属性命令，然后在兼容性页面中勾选“以管理员身份运行此程序”：\r\n\r\n启动Foxtable后，将下面的代码复制到命令窗口执行：\r\n\r\nHttpServer.Prefixes.Add(\"http://127.0.0.1/\")\r\nHttpServer.Start()\r\n\r\n然后在菜单的“管理项目”功能区，单击“网络监视器”，将“HttpRequest”事件的代码设置为：\r\n\r\ne.WriteString(\"Hello\r\nWorld\")\r\n\r\n现在打开你的浏览器，输入地址：http://127.0.0.1，会显示如下内容：\r\n\r\n是的，我们只用了3行代码，就建立了一个简单的http服务系统。\r\n\r\n通过下面的代码，可以关闭http服务：\r\n\r\nHttpServer.Close()\r\n\r\n现在为了接下来的测试方便，我们可以设计一个窗口，窗口有两个按钮，分别用于开启和关闭http服务：\r\n\r\n按钮的代码就是前面介绍的，就不重复了。\r\n\r\n注意每次开启新的服务之前，都必须关闭之前的服务，否则会无效或出现错误提示。\r\n\r\n## 服务器IP\r\n\r\n服务器IP\r\n\r\n\r\n**服务器IP**\r\n\r\n上一节我们搭建了一个简单的http服务，你只能在本机访问这个http服务，因为127.0.0.1表示的是本机IP。\r\n\r\n我们可以设置多个IP，假定服务器的局域网IP是：192.168.0.100，我们可以将开启服务按钮的代码改为：\r\n\r\nHttpServer.Prefixes.Add(\"http://127.0.0.1/\")\r\nHttpServer.Prefixes.Add(\"http://192.168.0.100/\")\r\nHttpServer.Start()\r\n\r\n这样局域网中的其他电脑，就能通过局域网IP来访问我们的http服务了：\r\n\r\n如果服务器有公网IP，一样可以加入，这样外网用户就能通过公网IP来访问我们的http服务。\r\n\r\n如果服务器有多个IP，可以用下面的代码来开启http服务：\r\n\r\nHttpServer.Prefixes.Add(\"http://\\*/\")\r\nHttpServer.Start()\r\n\r\n这样用户就可以通过指向服务器的任何一个IP来访问http服务，包括本机IP、局域网IP和公网IP。\r\n\r\n建议今后都采用以上代码来开启http服务，这样就无需因IP的变动而修改代码了。\r\n\r\n如果是公网，可以设置域名指向你的服务器，让用户通过域名来访问你的http服务。\r\n\r\n提示：为了测试方便，接下来所有例子，都将通过127.0.0.1来访问。\r\n\r\n## 服务器端口\r\n\r\n服务器端口\r\n\r\n\r\n**服务器端口**\r\n\r\n如果我们不指定端口，那么http服务使用的就是默认的端口80。\r\n\r\n下面的代码：\r\n\r\nHttpServer.Prefixes.Add(\"http://\\*/\")\r\nHttpServer.Start()\r\n\r\n等同于：\r\n\r\nHttpServer.Prefixes.Add(\"http://\\*:80/\")\r\nHttpServer.Start()\r\n\r\n有时，我们需要给自己的http服务更换端口。\r\n例如你公司已经有了一台web服务器，现在你需要利用这台服务器运行Foxtable的http服务，那么就不能使用80端口了，因为这样会和原来的web服务发生冲突。\r\n\r\n换个端口开启http服务很简单，例如：\r\n\r\nHttpServer.Prefixes.Add(\"http://\\*:32177/\")\r\nHttpServer.Start()\r\n\r\n现在你要访问这个http服务，地址必须加上端口号才行：\r\n\r\n为了方便，就下来的例子还是使用默认的端口80。\r\n\r\n## 增加入站规则\r\n\r\n入站规则\r\n\r\n\r\n**入站规则**\r\n\r\n如果你在另一台电脑访问我们新建立的http服务，很可能无法访问。\r\n\r\n通常这是防火墙造成的，我们可以新建一个入站规则，让防火墙允许客户端访问我们的http服务。\r\n\r\n以windows 10自带的防火墙为例，新建一个入站规则的步骤为：\r\n\r\n1、在Windows的控制面板，打开“Windows 防火墙”。\r\n\r\n2、在“Windows 防火墙”窗口，单击“高级设置”。\r\n\r\n3、在“高级设置”窗口，单击“入站规则”，然后在右侧单击“新建规则”。\r\n\r\n4、选择规则类型为“端口”：\r\n\r\n5、协议类型选择TCP，并输入你的http服务所采用的端口号：\r\n\r\n5、接下来选择允许接入：\r\n\r\n6、应用规则勾选所有：\r\n\r\n7、最后输入规则名称即可，名称可以随便取，最好方便识别。\r\n\r\n## 开启多个服务\r\n\r\n开启多个服务\r\n\r\n\r\n**开启多个服务**\r\n\r\n你可以一次开启多个http服务，例如：\r\n\r\nHttpServer.Prefixes.Add(\"http://\\*/\")\r\nHttpServer.Prefixes.Add(\"http://\\*:32177/\")\r\nHttpServer.Prefixes.Add(\"http://192.168.0.100:32188/\")\r\nHttpServer.Start()\r\n\r\n上面的代码开启了三个http服务，头两个服务可以通过指向服务器的任何ip来访问，例如：\r\n\r\nhttp://127.0.0.1/\r\nhttp://192.168.0.100/\r\n\r\nhttp://127.0.0.1:32177/\r\nhttp://192.168.0.100:32177/\r\n\r\n第三个服务则只能根据固定的IP地址访问：\r\n\r\nhttp://192.168.0.100:32188/\r\n\r\n重要提示：当你通过多个IP开启服务时，只要有一个IP错误，所有服务都是不能启动的。\r\n\r\n## 显示不同的内容\r\n\r\n显示不同的内容\r\n\r\n\r\n**显示不同的内容**\r\n\r\n在上一节中，我们开启了三个http服务，当我们分别访问这三个服务时，显示的内容都是“Hello Word”。\r\n显然，这不符合实际需要，真正的http服务应该能根据用户输入的不同地址，显示不同的内容。\r\n\r\nHttpRequest事件的e参数有很多，其中有三个e参数，用于获取用户访问路径的各部分。\r\n\r\nHost: 返回IP地址(或域名)\r\nPort：返回端口号\r\nPath：返回路径，含文件名。\r\n\r\n假定用户输入的访问路径是\"http://192.168.0.100:32188/fox/\",这三个e参数的返回值分别是：\r\n\r\nHost: 192.168.0.100\r\nPort: 32188\r\nPath: fox\r\n\r\n假定用户输入访问路径是\"http://192.168.0.100:32188/fox/china.htm\"，这三个e参数的返回值分别是：\r\n\r\nHost: 192.168.0.100\r\nPort: 32188\r\nPath: fox\\china.htm\r\n\r\n注意：\r\n\r\n1、e参数path中的路径分割符号是\"\\\"，不是\"/\"。\r\n2、尽量通过路径和文件名来区分用户的访问请求，不要占用太多端口，因为端口是有限的。\r\n\r\n现在就可以根据用户访问的不同端口、路径和和文件，甚至是不同的IP，来显示不同的内容，例如将HttpRequest的代码设置为：\r\n\r\nIf\r\ne.host =\r\n\"127.0.0.1\" Then\r\n    e.WriteString(\"你是本地用户\")\r\nElse\r\n    Select Case\r\ne.Port\r\n        Case 80\r\n            e.WriteString(\"Hello\r\nWorld\")\r\n\r\nCase\r\n32177\r\n            e.WriteString(\"你好,世界\")\r\n        Case 32188\r\n            If\r\ne.path = \"fox\"\r\nThen\r\n                e.WriteString(\"你好,狐表用户\")\r\n            ElseIf e.path\r\n= \"fox\\china.htm\" Then\r\n'注意这里的路径分隔符是\"\\\"\r\n                e.WriteString(\"你好,中国狐表用户\")\r\n\r\nElse\r\n                e.WriteString(\"你好,未找到网页\")\r\n            End If\r\n        Case\r\nElse\r\n            e.WriteString(\"你好,端口错误\")\r\n    End\r\nSelect\r\nEnd\r\nIf\r\n\r\n如果你通过地址“http://192.168.0.100:32188/fox”访问http服务，将显示:\r\n\r\n如果你通过地址\"http://192.168.0.100:32188/fox/china.htm\"访问http服务，将显示：\r\n\r\n## 关于WriteString\r\n\r\n关于WriteString\r\n\r\n\r\n**关于WriteString**\r\n\r\n在前面的例子中，在HttpReuest用下面的代码：\r\n\r\ne.WriteString(\"Hello\r\nWorld\")\r\n\r\n在浏览器中显示了下面的内容：\r\n\r\n需要注意的是，每次只能执行一次WriteString或WriteFile(后面会介绍)，例如下面的代码是无法正常运行的：\r\n\r\ne.WriteString(\"Hello\r\nWorld\")\r\ne.WriteString(\"Hello\r\nChina\")\r\n'这一行会出错\r\n\r\n需要将两个字符串合并为一个，再用WriteString写入浏览器，例如：\r\n\r\nDim\r\nrs As String = \"Hello World\"\r\nrs = rs\r\n& vbcrlf\r\n& \"Hello China\"\r\ne.WriteString(rs)\r\n\r\n在浏览器中显示的内容是：\r\n\r\n你肯定会奇怪，vbcrlf是回车换行，按理应该分两行显示，怎么上面的内容只显示了一行呢？\r\n这就是涉及到HTML标签的知识，在HTML中，连续的多个空格或换行，都会被显示为1个空格，而换行用<br/>表示。\r\n\r\n所以要分开两行显示，正确的HttpReuest事件代码是：\r\n\r\nDim\r\nrs As\r\nString =\r\n\"Hello World\"\r\nrs =\r\nrs &\r\n\"<br/>Hello China\"\r\ne.WriteString(rs)\r\n\r\n在浏览器显示的内容是：\r\n\r\n如果要分段显示文字，可以将段落放在<p>和</p>之间，例如将HttpReuest事件代码设置为：\r\n\r\nDim\r\nrs As\r\nString =\r\n\"<p>Foxtable首先是一个优秀的应用软件,你不需要编写任何代码,即可高效完成日常数据管理工作.</p>\"\r\nrs = rs\r\n&\r\n\"<p>同时Foxtable又是一个高效的.net平台开发工具,专门针对数据管理软件的开发作了大量的优化</p>\"\r\ne.WriteString(rs)\r\n\r\n在浏览器显示的结果为：\r\n\r\n用a标签可以插入超链接，例如将HttpReuest事件代码设置为：\r\n\r\nDim\r\nrs As\r\nString =\r\n\"<a href='http://www.foxtable.com/'>单击此处</a>访问Foxtable主页\"\r\ne.WriteString(rs)\r\n\r\n<a>标签的href属性指定链接地址，属性用单引号括起来，<a>和</a>之间为要显示的文本，上述代码在浏览器显示的结果为：\r\n\r\nHTML的标签有很多，我们并不打算一一介绍，教会大家HTML不是我们的任务，有人比我们做得更好，有兴趣的话，可以访问：\r\n\r\n<http://www.w3school.com.cn/html/index.asp>\r\n\r\n通常不超过半天时间就可以掌握了。\r\n\r\n## 关于网页设计\r\n\r\n关于网页设计\r\n\r\n\r\n**关于网页设计**\r\n\r\n上一节我们接触了一些常用的HTML标签，但我们不会有专门的章节来介绍HTML标签。\r\n因为我们并不打算教会大家网页设计，这不是我们的长处，已经有很多比我们更擅长的人在做这项工作。\r\n我只想告诉大家，如果你掌握了网页设计，你之前所有的知识，包括HTML、CSS、JavaScript和JQuery等等，在Foxtable都能继续派上用场。\r\n\r\n**可以不会网页设计**\r\n\r\n如果你完全不会网页设计，也没有关系，Foxtable内置了网页自动生成功能，不需要有任何的网页设计知识，一样可以设计出具备专业效果的网页来。\r\n\r\n例如你只需寥寥十来行代码，就可以设计出下面的网页来：\r\n\r\n如果你想提前了解一下，可以参考：[使用框架生成网页](0011.htm)\r\n\r\n**会网页设计更好**\r\n\r\n自动生成，意味着轻松，也意味着约束，如果你懂一些网页设计，你就可以突破这种约束，甚至可以完全放弃Foxtable的自动网页生成功能。\r\n\r\n如果你打算学习网页设计，我给你推荐一个网站：<http://www.w3school.com.cn/h.asp>\r\n\r\n对于一般用户来说，花一两天的时间掌握[HTML](http://www.w3school.com.cn/html/index.asp)、[HTML5](http://www.w3school.com.cn/html5/index.asp)和[CSS](http://www.w3school.com.cn/css/index.asp)，就足够使用了，如果想做一些动态的效果，可以再掌握一下[JavaScript](http://www.w3school.com.cn/js/index.asp)和[CSS3](http://www.w3school.com.cn/css3/index.asp)。\r\n\r\n## 使用StringBuilder\r\n\r\n使用StringBuilder\r\n\r\n\r\n**使用StringBuilder**\r\n\r\n前面的例子，我们是直接将字符串组合起来，然后写入客户端的浏览器：\r\n\r\nDim\r\nrs As\r\nString =\r\n\"Hello World\"\r\nrs =\r\nrs &\r\n\"<br/>Hello China\"\r\ne.WriteString(rs)\r\n\r\n如果要写入的内容比较多，这种方法是比较低效的，因为.net合并字符串的效率并不好。\r\n\r\n我们建议用[StringBuilder](2097.htm)来合并字符串，例如：\r\n\r\nDim\r\nsb As\r\nNew\r\nStringBuilder\r\nsb.AppendLine(\"Hello\r\nWorld\")\r\nsb.AppendLine(\"<br/>Hello\r\nChina\")\r\ne.WriteString(sb.ToString)\r\n\r\n生成的网页内容越多，使用[StringBuilder](2097.htm)的效率优势就越明显，今后例子都将采用这种方式。\r\n\r\n## 用HTML生成网页\r\n\r\n用HTML生成网页\r\n\r\n\r\n**用HTML生成网页**\r\n\r\n本节内容适合已经掌握HTML的用户，其实不管你是否已经掌握HTML，建议都学习一下，因为：真的很简单！\r\n\r\n现在我们将HttpRequest事件代码改为：\r\n\r\nDim\r\nsb As\r\nNew\r\nStringBuilder\r\nsb.AppendLine(\"<form\r\nenctype='multipart/form-data' method='post'\r\nid='form1'\r\nname='form1'>\")\r\nsb.AppendLine(\"产品:\r\n<input name='cp' id='cp'><br/><br/>\")\r\nsb.AppendLine(\"客户:\r\n<input name='kh' id='kh'><br/><br/>\")\r\nsb.AppendLine(\"雇员:\r\n<input name='gy' id='gy'><br/><br/>\")\r\nsb.AppendLine(\"单价:\r\n<input type='number' name='dj' id='dj'><br/><br/>\")\r\nsb.AppendLine(\"折扣:\r\n<input type='number' name='zk' id='zk' min='0' max='0.15' step='0.01'><br/><br/>\")\r\nsb.AppendLine(\"数量:\r\n<input type='number' name='sl' id='sl'><br/><br/>\")\r\nsb.AppendLine(\"日期:\r\n<input type='date' name='rq' id='rq'><br/><br/>\")\r\nsb.AppendLine(\"<input\r\nType='submit' name='Sumbit' id='Sumbit' value='确定'>\")\r\nsb.AppendLine(\"</form>\")\r\ne.WriteString(sb.ToString)\r\n\r\n我们用上面的代码生成了一个表单，在Chorme浏览器中的显示效果为：\r\n\r\n即使你完全不懂HTML，看懂上面的代码也没有问题，以下面这一行代码为例：\r\n\r\nsb.AppendLine(\"折扣:\r\n<input type='number' name='zk' id='zk' min='0' max='0.15' step='0.01'><br/><br/>\")\r\n\r\n表示增加一个输入框，输入类型为数字(type='number')，允许最小值为0(min='0')，允许最大值为0.15(max='0.15'),允许输入精度为0.01(step='0.01')，id为zk(id='zk')，name为zk(name='zk')。\r\n\r\n在不同的浏览器，上述代码生成的页面会有不同的显示效果，例如对于日期输入框，Chorme可以显示一个下拉\r\n日历供选择，而且你只能输入日期，对于IE来说，这个日期输入框和普通的文本输入框没有任何差别。\r\n\r\n教会你HTML不是我的任务，有人干这个我们擅长很多，如果你有兴趣，可以访问：<http://www.w3school.com.cn/html/index.asp>，通常不会超过半天时间就能基本掌握。\r\n\r\n## 不用StringBuilder\r\n\r\n不用StringBuilder\r\n\r\n\r\n**不用StringBuilder**\r\n\r\n本文档的网页绝大多数都是用StringBuilder的AppendLine组合合成，这是因为网页要和Foxtable的数据结合，用StringBuilder适合运行过程中动态合成。\r\n\r\n我们也可以直接发送，例如：\r\n\r\nDim\r\nhtml\r\nAs\r\n\r\nString\r\n=\r\n<![CDATA[\r\n<form enctype='multipart/form-data' method='post' id='form1' name='form1'>\r\n产品:\r\n<input name='cp' id='cp'><br/><br/>\r\n客户:\r\n<input name='kh' id='kh'><br/><br/>\r\n雇员:\r\n<input name='gy' id='gy'><br/><br/>\r\n单价:\r\n<input type='number' name='dj' id='dj'><br/><br/>\r\n折扣:\r\n<input type='number' name='zk' id='zk' min='0' max='0.15' step='0.01'><br/><br/>\r\n数量:\r\n<input type='number' name='sl' id='sl'><br/><br/>\r\n日期:\r\n<input type='date' name='rq' id='rq'><br/><br/>\r\n<input Type='submit' name='Sumbit' id='Sumbit' value='确定'>\r\n</form>\r\n]]>.Value\r\ne.WriteString(html)\r\n\r\n结果和上一节完全相同。\r\n\r\n请注意，第一行的<![CDATA[和最后一行的]]>.Value是语法要求，并不包括在网页内容中，切记。\r\n\r\n对于少量需要动态改变的内容，可以先用字符串的Replace方法查找替换，然后再发送。\r\n\r\n## 生成结构完整的网页\r\n\r\n生成结构完整的网页\r\n\r\n\r\n**生成结构完整的网页**\r\n\r\n在浏览器打开我们上一节生成的网页，右击，查看源代码，可以看到其代码为：\r\n\r\n没有head？ 没有body？ 如果你接触过HTML网页设计，你会知道这不是一个结构完整的网页。\r\n\r\n你也不要担心，正如我们所演示的，目前所有的浏览器，都能正确解释上面的代码。\r\n\r\n不过遵循规范总是没有错，我们来改进一下HttpRequst事件的代码：\r\n\r\n|  |\r\n| --- |\r\n| Dim html  As  String =  <![CDATA[  <!doctype html>  <html>  <head>  <meta charset='utf-8'>  <title>表单</title>  </head>  <body>  <form enctype='multipart/form-data' method='post' id='form1' name='form1'>  产品: <input name='cp' id='cp'><br/><br/>  客户: <input name='kh' id='kh'><br/><br/>  雇员: <input name='gy' id='gy'><br/><br/>  单价: <input Type='number' name='dj' id='dj'><br/><br/>  折扣: <input Type='number' name='zk' id='zk' min='0' max='0.15' step='0.01'><br/><br/>  数量: <input Type='number' name='sl' id='sl'><br/><br/>  日期: <input Type='date' name='rq' id='rq'><br/><br/>  <input Type='submit' name='Sumbit' id='Sumbit' value='确定'>  </form>  </body>  </html>  ]]>.Value  e.WriteString(html) |\r\n\r\n现在生成的就是一个结构完整的网页，在浏览器看到的代码是：\r\n\r\n## 我想显示一个图片\r\n\r\n我想显示一个图片\r\n\r\n\r\n**我想显示一个图片**本节内容适合已经掌握HTML的用户，其实不管你是否已经掌握HTML，建议你学习一下，因为：真的很简单！\r\n\r\n在D盘建立一个名为web的目录，复制一个图片文件到这个目录，假定图片文件的名称是abc.jpg。\r\n\r\n我们将HttpRequest事件的代码改为：\r\n\r\nDim\r\nsb As\r\nNew\r\nStringBuilder\r\nsb.Appendline(\"我想显示一个图片<br/>\")\r\nsb.AppendLine(\"<img\r\nsrc='abc.jpg'>\")\r\ne.WriteString(sb.ToString())\r\n\r\n在浏览器显示的结果为：\r\n\r\nimg标签用于显示图片，其src属性为要显示的图片文件名称。\r\n\r\n不过图片并没有显示，这是意料之中的，因为服务端并没有发送图片文件到浏览器。\r\n\r\n浏览器其实是分两步来显示这个网页的：\r\n\r\n1、首先获取网页内容：\r\n\r\n我想显示一个图片<br/>\r\n<img src='abc.jpg'>\r\n\r\n2、然后浏览器解析网页内容，发现要显示一个图片\"abc.jpg\"，如是又向服务器发送访问请求：\r\n\r\nhttp://127.0.0.1/abc.jpg\r\n\r\n要显示图片，服务器必须对这个访问请求对出响应，将后台的图片\"abc.jpg\"发送给客户端的浏览器，浏览器则显示收到的图片。\r\n\r\n实际上不单单是图片，网页中引用的任何文件，包括js、css等等，都会单独向服务器发出下载这个文件的请求，服务器必须对此做出响应。\r\n\r\n据此，我们修改HttpRequest事件代码为：\r\n\r\nDim\r\nfl As\r\nString =\r\n\"d:\\web\\\" &\r\ne.path '合成含路径的文件名\r\nIf\r\nfilesys.FileExists(fl) '如果是请求一个已经存在的文件\r\n    e.WriteFile(fl) '则发送此文件\r\nElse\r\n    Dim sb\r\nAs New\r\nStringBuilder\r\n    sb.Appendline(\"我想显示一个图片<br/><br/>\")\r\n    sb.AppendLine(\"<img\r\nsrc='abc.jpg'>\")\r\n    e.WriteString(sb.ToString())\r\nEnd\r\nIf\r\n\r\n现在浏览器可以正常显示图片了：\r\n\r\n提示：HttpRequest事件的WriteFile方法用于向客户端发送本地文件。\r\n\r\n**安全问题**\r\n\r\n上面的代码是不严谨的，因为用户可以下载\"d:\\web\"目录下的所有文件，包括非图片文件。\r\n\r\n我们需要进行一些判断，限制用户只能下载图片文件，为此可以将HttpRequest事件代码改为：\r\n\r\nDim\r\nfl As\r\nString =\r\n\"d:\\web\\\" &\r\ne.path\r\nIf\r\nfilesys.FileExists(fl) '如果是请求一个已经存在的文件\r\n    Dim idx\r\nAs Integer\r\n= fl.LastIndexOf(\".\")\r\n    Dim ext\r\nAs String\r\n= fl.SubString(idx)\r\n    Select Case\r\next\r\n        Case\r\n\".jpg\",\".gif\",\".png\",\".bmp\",\".wmf\"\r\n '只允许请求图片文件\r\n            e.WriteFile(fl)\r\n    End\r\nSelect\r\nElse\r\n    Dim sb\r\nAs New\r\nStringBuilder\r\n    sb.Appendline(\"我想显示一个图片<br/><br/>\")\r\n    sb.AppendLine(\"<img\r\nsrc='abc.jpg'>\")\r\n    e.WriteString(sb.ToString())\r\nEnd\r\nIf\r\n\r\n## 文件引用的路径问题\r\n\r\n文件引用的路径问题\r\n\r\n\r\n**文件引用的路径问题**\r\n\r\n对于稍具规模的系统，为便于维护，我们很少会将所有文件放在同一个文件夹中。\r\n\r\n那么某一文件夹中的网页，如何引用其他文件夹中的文件呢。\r\n\r\n**使用绝对路径**\r\n\r\n如果被引用文件的路径是固定的，那么使用绝对路径即可。\r\n\r\n假定\"d:\\web\"为网页根目录，图片文件\"abc.jpg\"位于该目录的\"images\"子目录下。\r\n那么在任何路径的网页，都可以通过路径\"/images/abc.jpg\"引用此图片，\"/\"表示根目录\"d:\\web\"，所以\"/images/abc.jpg\"就表示\"d:\\web\\images\\abc.jpg\"。\r\n\r\n**示例**\r\n\r\n现在将文件\"abc.jpg\"复制到目录\"d:\\web\\images\"下，然后将HttpRequest事件代码改为：\r\n\r\nDim\r\nfl As\r\nString = \"d:\\web\\\"\r\n& e.path\r\nIf\r\nfilesys.FileExists(fl)\r\n    Dim idx\r\nAs Integer =\r\nfl.LastIndexOf(\".\")\r\n    Dim ext\r\nAs String  =\r\nfl.SubString(idx)\r\n    Select Case\r\next\r\n        Case\r\n\".jpg\",\".gif\",\".png\",\".bmp\",\".wmf\"\r\n            e.WriteFile(fl)\r\n    End\r\nSelect\r\nElse\r\n    Dim sb\r\nAs New\r\nStringBuilder\r\n    sb.Appendline(\"我想显示一个图片<br/><br/>\")\r\n    Select Case\r\ne.path\r\n        Case \"a.htm\"\r\n            sb.AppendLine(\"<img\r\nsrc='/images/abc.jpg'>\")\r\n        Case \"sub1\\a.htm\"\r\n            sb.AppendLine(\"<img\r\nsrc='/images/abc.jpg'>\")\r\n         Case\r\n\"sub1\\sub2\\a.htm\"\r\n            sb.AppendLine(\"<img\r\nsrc='/images/abc.jpg'>\")\r\n    End Select\r\n    e.WriteString(sb.ToString())\r\nEnd\r\nIf\r\n\r\n现在你通过下面三个网页访问HttpServer：\r\n\r\nhttp://127.0.0.1/a.htm\r\nhttp://127.0.0.1/sub1/a.htm\r\nhttp://127.0.0.1/sub1/sub2/a.htm\r\n\r\n可以看到，三个不同路径的网页，使用相同的引用路径\"\"\\images\\abc.jpg\"\"，都可以正常显示图片\"d:\\web\\images\\abc.jpg\"。\r\n\r\n**使用相对路径**\r\n\r\n如果要使用相对路径，需要用到两个重要的符号，一个是表示网页所在的目录的\".\"，一个是表示父目录的\"..\"\r\n\r\n假定网页所在目录为\"d:\\web\"目录，图片文件位于该目录的images子目录下，也就是\"d:\\web\\images\"目录下，那么使用相对路径的代码为：\r\n\r\n<img src='./images/abc.jpg'>\r\n\r\n\".\"表示网页所在目录\"d:\\web\"，所以\"./images\"表示\"d:\\web\\images\"\r\n\r\n假定网页所在目录为\"d:\\web\\sale\"目录，图片文件位于\"d:\\web\\images\"目录下，\r\n那么使用相对路径的代码为：\r\n\r\n<img src='../images/abc.jpg'>\r\n\r\n\"..\"表示\"d:\\web\\sale\"的父目录，也就是\"d:\\web\"，\r\n所以\"../images\"表示\"d:\\web\\images\"。\r\n\r\n**示例**\r\n\r\n现在将文件\"abc.jpg\"复制到目录\"d:\\web\\images\"下，然后将HttpRequest事件代码改为：\r\n\r\nDim\r\nfl As\r\nString =\r\n\"d:\\web\\\" &\r\ne.path\r\nIf\r\nfilesys.FileExists(fl)\r\n    Dim idx\r\nAs Integer\r\n= fl.LastIndexOf(\".\")\r\n    Dim ext\r\nAs String\r\n= fl.SubString(idx)\r\n    Select Case\r\next\r\n        Case\r\n\".jpg\",\".gif\",\".png\",\".bmp\",\".wmf\"\r\n            e.WriteFile(fl)\r\n    End\r\nSelect\r\nElse\r\n    Dim sb\r\nAs New\r\nStringBuilder\r\n    sb.Appendline(\"我想显示一个图片<br/><br/>\")\r\n    Select\r\nCase e.path\r\n        Case\r\n\"a.htm\"\r\n            sb.AppendLine(\"<img\r\nsrc='.\\images\\abc.jpg'>\")\r\n        Case \"sub1\\a.htm\"\r\n            sb.AppendLine(\"<img\r\nsrc='..\\images\\abc.jpg'>\")\r\n         Case\r\n\"sub1\\sub2\\a.htm\"\r\n            sb.AppendLine(\"<img\r\nsrc='..\\..\\images\\abc.jpg'>\")\r\n    End Select\r\n    e.WriteString(sb.ToString())\r\nEnd\r\nIf\r\n\r\n现在你通过下面三个网页访问HttpServer：\r\n\r\nhttp://127.0.0.1/a.htm\r\nhttp://127.0.0.1/sub1/a.htm\r\nhttp://127.0.0.1/sub1/sub2/a.htm\r\n\r\n都可以正常显示\"d:\\web\\images\"的图片\"abc.jpg\".\r\n\r\n## 使用JavaScript文件\r\n\r\n使用JavaScript文件\r\n\r\n\r\n**使用JavaScript文件**\r\n\r\n本节内容要使用到JavaScript。\r\n\r\n即使您没有掌握JavaScript，也没有关系，因为真的很简单。\r\n\r\n假定在以下网页中，希望输入单价、折扣和数量后，能即时计算出金额：\r\n\r\n要完成这个任务，就需要使用Javascript来编写脚本了。\r\n\r\n**知识准备**\r\n\r\n下面会对本节要使用的JavaScript知识做一个简单的介绍。\r\n\r\n教会你JavaScript，并不是我们的任务，\r\n如果你有兴趣进行进一步的了解，可以访问：<http://www.w3school.com.cn/b.asp>\r\n\r\n我个人建议你花上一两天时间，通过上述网页，掌握一下JavaScript的基础知识。\r\n\r\n首先是一个重要的提示：JavaScript是区分大小写的，例如calc和Calc是两个不同的变量或函数，逻辑值是true和false，写成True和False，也是不行的。\r\n\r\n**1、如何引用表单元素**\r\n\r\n我们在设计表单的时候，需要给输入框指定id属性和name属性，例如：\r\n\r\nsb.appendLine(\"单价:\r\n<input Type='number' name='dj' id='dj'>\")\r\n\r\n表示定义了一个输入框用于输入单价，这个输入框的id和name都是\"dj\"，id和name属性可以设置为不同的值，除非有特殊需要，一般设置为相同的值即可。\r\n\r\n我们可以在js代码中直接通过id属性引用这个输入框，例如：\r\n\r\ndj.value\r\n\r\n表示单价输入框的值，这是一种简写方式，使用起来很方便。\r\n\r\n还有一种更为标准的写法，例如下面的代码，同样是表示单价输入框的值：\r\n\r\ndocument.getElementById('dj').value\r\n\r\n前一种方法更为简洁，但后一种方法更为标准，多数编程书籍使用的都是后一种方法，本帮助文件两种都用。\r\n\r\n**2、网页事件**\r\n\r\n网页中所有元素都是有事件的，例如单击按钮，会触发按钮的onclick事件，修改输入框的内容，会触发输入框的onchange事件。\r\n\r\n我们可以在设计网页的时候，指定事件触发后要执行的代码，例如：\r\n\r\nsb.appendLine(\"单价:\r\n<input Type='number' name='dj' id='dj' onchange='calc()'>\")\r\n\r\n\r\n表示修改单价输入框的内容后，执行calc函数，注意函数名是区分大小写的，括号也是不能省略的。\r\n\r\n**3、在哪里写代码**\r\n\r\nJavaScript代码一般放在单独的文本文件中，后缀名为js，你可以直接用记事本编写js代码，我个人喜欢结合nodepad++和Dreamweaver编写，前者可以让我更方便地分析代码结构，后者可以帮我检查出语法错误。\r\n\r\n编写好的js代码一般放在某个子目录下，例如本帮助文件所有的js代码，都放在\"d:\\web\\lib\"目录下。\r\n\r\n编写好的js代码，并不会自动自动生效，我们在设计网页的时候，还需要将编写好的js代码文件，引入到网页中，例如：\r\n\r\nsb.appendline(\"<script src='./lib/calc.js'></script>\")\r\n\r\n表示将lib子目录下的calc.js文件引入到网页中。\r\n\r\n有了上面的知识，我们就能很轻松地完成自动计算金额的任务了。\r\n\r\n**设计步骤**\r\n\r\n1、在\"d:\\web\"目录下，建立一个子目录lib，在这个子目录中新建一个文本文件，文件名为\"calc.js\"，文件内容为：\r\n\r\nfunction calc(){\r\n    je.value = dj.value \\* sl.value \\* (1 - zk.value);\r\n}\r\n\r\n上述的代码定义了一个名为calc的函数，用于根据单价(dj)、数量(sl)和折扣(zk)的值，计算出金额。\r\n\r\n2、然后修改HttpRequest事件的代码，在有变动的地方，我加上了注释：\r\n\r\nDim\r\nfl As\r\nString = \"d:\\web\\\"\r\n& e.path\r\nIf\r\nfilesys.FileExists(fl)\r\n\r\nDim idx\r\nAs Integer =\r\nfl.LastIndexOf(\".\")\r\n\r\nDim ext\r\nAs String  =\r\nfl.SubString(idx)\r\n\r\nSelect Case\r\next\r\n\r\nCase \".jpg\",\".gif\",\".png\",\".bmp\",\".wmf\",\".js\"\r\n'这里加上了js扩展名\r\n\r\ne.WriteFile(fl)\r\n\r\nEnd\r\nSelect\r\nElse\r\n\r\nDim\r\nhtml\r\nAs\r\n\r\nString\r\n=\r\n<![CDATA[\r\n    <!doctype html>\r\n    <html>\r\n    <head>\r\n    <meta charset='utf-8'>\r\n    <title>表单</title>\r\n    </head>\r\n    <body>\r\n    <form enctype='multipart/form-data' method='post' id='form1'\r\nname='form1'>\r\n    产品:\r\n<input name='cp' id='cp'><br/><br/>\r\n    客户:\r\n<input name='kh' id='kh'><br/><br/>\r\n    雇员:\r\n<input name='gy' id='gy'><br/><br/>\r\n    单价:\r\n<input Type='number' name='dj' id='dj' onchange='calc()'><br/><br/>\r\n    折扣:\r\n<input Type='number' name='zk' id='zk' step='0.01' onchange='calc()'><br/><br/>\r\n    数量:\r\n<input Type='number' name='sl' id='sl' onchange='calc()'><br/><br/>\r\n    金额:\r\n<input Type='number' name='je' id='je' readonly><br/><br/>\r\n    日期:\r\n<input Type='date' name='rq' id='rq'><br/><br/>\r\n    <input Type='submit' name='Sumbit' id='Sumbit' value='确定'>\r\n    </form>\r\n    <script src='./lib/calc.js'></script>\r\n    </body>\r\n    </html>\r\n    ]]>.Value\r\n    e.WriteString(html)\r\nEnd If\r\n\r\n现在输入数量、单价和折扣，金额就会自动计算得出了：\r\n\r\n## 使用CSS文件\r\n\r\n使用CSS文件\r\n\r\n\r\n**使用CSS文件**\r\n\r\n本节内容适合已经掌握CSS的用户。\r\n\r\n同样，教会你CSS不是我们的任务，有人干这个我们擅长很多，如果有兴趣，可以访问：<http://www.w3school.com.cn/css/index.asp>，通常不会超过半天时间就能基本掌握。\r\n\r\n使用CSS文件和使用js文件的方法一样，只是CSS文件通常在head块引入，js通常在body块的结束位置引入。\r\n\r\n**一个例子**\r\n\r\n1、在\"d:\\web\"目录下，建立一个子目录css，在这个子目录中新建一个文本文件，文件名为文件名为“test.css”，文件内容为：\r\n\r\n.rd{color:red;}\r\n.gr{color:green;}\r\n\r\n2、然后修改HttpRequest事件的代码，在有变化的地方，我加上了注释：\r\n\r\nDim\r\nfl As\r\nString =\r\n\"d:\\web\\\" &\r\ne.path\r\nIf\r\nfilesys.FileExists(fl)\r\n    Dim idx\r\nAs Integer\r\n= fl.LastIndexOf(\".\")\r\n    Dim ext\r\nAs String\r\n= fl.SubString(idx)\r\n    Select Case\r\next\r\n        Case\r\n\".jpg\",\".gif\",\".png\",\".bmp\",\".wmf\",\".js\",\".css\"\r\n'这里加上了css扩展名\r\n            e.WriteFile(fl)\r\n    End\r\nSelect\r\nElse\r\n\r\nDim\r\nhtml\r\nAs\r\n\r\nString\r\n=\r\n<![CDATA[\r\n    <!doctype html>\r\n    <html>\r\n    <head>\r\n    <meta charset='utf-8'>\r\n    <title>CSS测试</title>\r\n    <link rel='stylesheet' href='./css/test.css'/>\r\n    </head>\r\n    <body>\r\n    <p class='rd'>This Is some text. This Is some text.</p>\r\n    <p class='gr'>This Is some text. This Is some text.</p>\r\n    </body>\r\n    </html>\r\n    ]]>.Value\r\n    e.WriteString(html)\r\nEnd\r\nIf\r\n\r\n在浏览器的显示结果为：\r\n\r\n## 使用第三方库\r\n\r\n使用第三方库\r\n\r\n\r\n**使用第三方库**\r\n\r\n一般用户可忽略本节内容。\r\n\r\n本节内容适合已经掌握JavaScript和CSS，希望使用第三方JavaScript库的用户。\r\n\r\n使用第三方的Javascript库和使用本地库并没有差别，下面是一个使用jQuery库的例子。\r\n\r\n1、在\"d:\\web\"目录下，建立一个子目录lib，在这个子目录中新建一个文本文件，文件名为\"test.js\"，文件内容为：\r\n\r\n$(document).ready(function(){\r\n    $('button').click(function(){\r\n        $('div').animate({left:'250px'});\r\n    });\r\n});\r\n\r\n2、HttpRequest事件代码设置为，在有变化的地方，我加上了注释：\r\n\r\nDim\r\nfl As\r\nString =\r\n\"d:\\web\\\" &\r\ne.path\r\nIf filesys.FileExists(fl)\r\n    Dim idx\r\nAs Integer\r\n= fl.LastIndexOf(\".\")\r\n    Dim ext\r\nAs String\r\n= fl.SubString(idx)\r\n    Select Case\r\next\r\n        Case\r\n\".jpg\",\".gif\",\".png\",\".bmp\",\".wmf\",\".js\",\".css\"\r\n\r\n            e.WriteFile(fl)\r\n    End\r\nSelect\r\nElse\r\n\r\nDim\r\nhtml\r\nAs\r\n\r\nString\r\n=\r\n<![CDATA[\r\n    <!doctype html>\r\n    <html>\r\n    <head>\r\n    <meta charset='utf-8'>\r\n    <title>jQuery测试</title>\r\n    <script src='https://libs.baidu.com/jquery/1.10.2/jquery.min.js'></script>\r\n    <script src='./lib/test.js'></script>\r\n    </head>\r\n    <body>\r\n    <button>开始动画</button><br/><br/>\r\n    <div style='background:#98bf21;height:100px;width:100px;position:absolute;'>\r\n    </body>\r\n    </html>\r\n    ]]>.Value\r\n\r\ne.WriteString(html)\r\nEnd\r\nIf\r\n\r\n在浏览器显示的结果如下，单击“开始动画”按钮，绿色方框会移动：\r\n\r\n## 使用设计好的网页\r\n\r\n使用设计好的网页\r\n\r\n\r\n**使用设计好的网页**\r\n\r\n前面的例子，我们都是用Foxtable动态合成网页。\r\n\r\n动态合成的好处是：网页是“活”的，同样的页面，根据不同的用户、数据和业务逻辑，显示内容和结构可以完全不同。\r\n\r\n我们也可以用第三方网页设计工具事先设计好网页，由Foxtable负责将网页发送给用户浏览器，代码很简单。\r\n\r\n例如你将设计好的网页，放在\"d:\\web\"目录及其子目录下，包括图片、js文件、css文件等等。\r\n\r\n然后将HttpRequest事件代码设置为：\r\n\r\nDim\r\nfl As\r\nString =\r\n\"d:\\web\\\" &\r\ne.path\r\nIf\r\nfilesys.FileExists(fl)\r\n    Dim idx\r\nAs Integer\r\n= fl.LastIndexOf(\".\")\r\n    Dim ext\r\nAs String\r\n= fl.SubString(idx)\r\n    Select Case\r\next\r\n        Case\r\n\".jpg\",\".gif\",\".png\",\".bmp\",\".wmf\",\".js\",\".css\"\r\n,\".html\",\".htm\"\r\n            e.WriteFile(fl)\r\n    End\r\nSelect\r\nEnd\r\nIf\r\n\r\n不到10行代码，这可能是史上最简单的架设http服务的方法。\r\n不，这不是最简单的，很快你会看到真正简单的http服务架设方法，只需三行代码。\r\n\r\n**静态与动态**\r\n\r\n如果网页需要和动态的数据以及业务逻辑相结合，就用代码动态生成；如果网页基本不变化，就事先设计好网页，也就是静态网页，以提高效率。\r\n\r\n同一个系统，可以同时有静态网页和动态网页，除非你明确告诉客户，否则客户并不会感受到这些页面什么不同。\r\n\r\n例如：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"\"\r\n        e.WriteString(\"这是根目录下的默认页面,是动态生成的\")\r\n    Case \"order.htm\"\r\n        e.WriteString(\"这是根目录下的Order.htm,这个文件并不存在,是自动生成的\")\r\n    Case \"sale\"\r\n        e.WriteString(\"这是根目录的sale子目录下的默认页面,是动态生成的\")\r\n    Case \"sale\\add.htm\"\r\n        e.WriteString(\"这是根目录的sale子目录下的add.htm,这个文件并不存在,是自动生成的\")\r\n    Case Else\r\n        Dim\r\nfl As\r\nString = \"d:\\web\\\"\r\n& e.Path\r\n        If\r\nfilesys.FileExists(fl)\r\n            Dim idx\r\nAs Integer\r\n= fl.LastIndexOf(\".\")\r\n            Dim ext\r\nAs String\r\n= fl.SubString(idx)\r\n            Select\r\nCase ext\r\n                Case\r\n\".jpg\",\".gif\",\".png\",\".bmp\",\".wmf\",\".js\",\".css\"\r\n,\".html\",\".htm\"\r\n                    e.WriteFile(fl)\r\n            End\r\nSelect\r\n        Else\r\n\r\ne.WriteString(\"好奇怪,你访问的页面不存在!\")\r\n        End\r\nIf\r\nEnd\r\nSelect\r\n\r\n有个小问题需要注意一下，e.path的路径分割符是\\\"，不是\"/\"。\r\n\r\n实际上，很难严格区分动态网页和动态网页，因为就是动态生成的网页，也需要使用不少静态的文件，例如图片、js、css等等。\r\n\r\n## 再谈WriteString\r\n\r\n再谈WriteString\r\n\r\n\r\n**再谈WriteString**\r\n\r\n**指定内容类型**\r\n\r\n前面讲到了通过WriteFile发送设计好的JS、CSS和HTML文件。\r\n\r\n如果文件内容很小，那么可以直接通过WriteString发送，通过第二个参数指定内容类型。\r\n\r\n例如：\r\n\r\ne.WriteString(\"function\r\nabc(){alert('你好！');}\",\r\n\r\n\"text/javascript\")\r\n\r\n**发送长文本**\r\n\r\n本文档有很多网页使用StringBuilder的AppendLine方法拼接，这是因为网页要和Foxtable的数据结合，用StringBuilder适合运行过程中动态合成。\r\n\r\n如果不需要拼接，就可以直接发送整个网页，例如：\r\n\r\nDim\r\nhtml\r\n\r\nAs\r\n\r\n\r\nString\r\n=\r\n\r\n<![CDATA[\r\n<form enctype='multipart/form-data' method='post' id='form1' name='form1'>\r\n产品:\r\n<input name='cp' id='cp'><br/><br/>\r\n客户:\r\n<input name='kh' id='kh'><br/><br/>\r\n雇员:\r\n<input name='gy' id='gy'><br/><br/>\r\n单价:\r\n<input type='number' name='dj' id='dj'><br/><br/>\r\n折扣:\r\n<input type='number' name='zk' id='zk' min='0' max='0.15' step='0.01'><br/><br/>\r\n数量:\r\n<input type='number' name='sl' id='sl'><br/><br/>\r\n日期:\r\n<input type='date' name='rq' id='rq'><br/><br/>\r\n<input Type='submit' name='Sumbit' id='Sumbit' value='确定'>\r\n</form>\r\n]]>.Value\r\ne.WriteString(html)\r\n\r\n请注意：\r\n\r\n1、第一行的<![CDATA[和最后一行的]]>.Value是语法要求，并不包括在网页内容中，切记。\r\n\r\n2、对于少量需要动态改变的内容，可以先用字符串的Replace方法查找替换，然后再发送，对于需要大量替换的，还是用StringBuilder替换会更高效一些。\r\n\r\n3、默认的内容类型就是\"text/html\"，所以这里可以省略WriteString的第二个参数。\r\n\r\n## 网页出现乱码啦\r\n\r\n网页出现乱码啦\r\n\r\n\r\n**网页出现乱码啦**\r\n\r\n我用古老的FrontPage中文版设计了一个网页，这个网页很简单，就一行文字：\r\n\r\n我喜欢用Foxtable开发管理软件\r\n\r\n我将这个网页复制到\"d:\\web\"目录下，通过HttpRequest将这个网页发送到客户端的浏览器上，意外出现了，中文全部变成了乱码：\r\n\r\n如果我们在本机双击这个文件，浏览器是能正常显示中文的，说明网页本身是没有问题的：\r\n\r\n为了查明原因，我用Nodepad++（也可以用记事本）打开这个网页，看到的源代码是：\r\n\r\n现在出现中文乱码的原因清楚了，因为HttpRequest并不会检查要发送网页的编码格式，默认一律采用utf-8编码格式读取网页内容，然后发送到用户浏览器。\r\n\r\n为解决这个问题，我们可以修改HttpRequest事件代码：\r\n\r\nDim\r\nfl As\r\nString = \"d:\\web\\\"\r\n& e.Path\r\nIf\r\nfilesys.FileExists(fl)\r\n    Dim idx\r\nAs Integer =\r\nfl.LastIndexOf(\".\")\r\n    Dim ext\r\nAs String  =\r\nfl.SubString(idx)\r\n    Select Case\r\next\r\n        Case\r\n\".jpg\",\".gif\",\".png\",\".bmp\",\".wmf\",\".js\",\".css\"\r\n,\".html\",\".htm\",\".doc\",\".docx\",\".xls\",\".xlsx\",\".pdf\"\r\n            e.ResponseEncoding\r\n= \"gb2312\"\r\n'设置网页编码为gb2312\r\n            e.WriteFile(fl)\r\n    End\r\nSelect\r\nElse\r\n    e.WriteString(\"好奇怪,你访问的页面不存在!\")\r\nEnd\r\nIf\r\n\r\n现在浏览器能正常显示中文了：\r\n\r\nHttpRequest的e参数ResponseEncoding，用于设置向客户端发送文本性质的内容时，所采用的编码格式。\r\n\r\n修改网页编码为utf-8格式\r\n\r\n你也可以不修改HttpRequest事件代码，使用utf-8编码保存网页即可，包括js和css文件，\r\n我们都建议采用utf-8编码。\r\n新的网页设计工具，多数默认编码就是utf-8格式。\r\n如果已经有其他编码格式的网页，需要改为utf-8编码，以上面的文件为例，步骤为：\r\n\r\n1、用记事本打开这个网页，找到charset=gb2312，改为charset=utf-8\r\n2、不要直接保存，在文件菜单单击\"另存为\"，在另存窗口选择编码格式为\"utf-8\"，然后单击\"确定\"按钮保存即可。\r\n\r\n## 接收表单数据\r\n\r\n接收表单数据\r\n\r\n\r\n**接收表单数据**\r\n\r\n多数时候，用户是通过表单输入数据的，表单类似于Foxtable的窗口。\r\n\r\n**定义表单**\r\n\r\n表单用<form>标签开始，用</form>标签结束，中间是各种输入元素。\r\n下面是一个表单的定义，表单名为form1，数据提交方式为post，数据接收页面是accept.htm:\r\n\r\n<form action='accept.htm' enctype='multipart/form-data' method='post' id='form1' name='form1'>\r\n\r\n</form>\r\n\r\n提示：表单的action属性是一个网页地址，用户输入完成后单击提交按钮，会将表单数据提交到这个网页。\r\n\r\n**Values属性**\r\n\r\nHttpRequest事件e参数有个Values属性，这是一个字典，包括所有用户提交的数据，键为输入元素的name属性，值就是输入元素的值。\r\n\r\n**一个例子**\r\n\r\n我们下面用一个例子看看Foxtable是如何定义表单和接收表单数据的，将HttpRequest事件代码设置为：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"input.htm\"\r\n\r\nDim\r\nhtml\r\nAs\r\n\r\nString\r\n=\r\n<![CDATA[\r\n        <form action='accept.htm' enctype='multipart/form-data' method='post'\r\nid='form1' name='form1'>\r\n        产品:\r\n<input name='cp' id='cp'><br/><br/>\r\n        客户:\r\n<input name='kh' id='kh'><br/><br/>\r\n        单价:\r\n<input type='number' name='dj' id='dj'><br/><br/>\r\n        数量:\r\n<input type='number' name='sl' id='sl'><br/><br/>\r\n        日期:\r\n<input type='date' name='rq' id='rq'><br/><br/>\r\n        密码:\r\n<input type='password' name='mm' id='mm'><br/><br/>\r\n        支付方式:\r\n<br/>\r\n        <input type='radio' name='fs' id='fs1' value = '支付宝'\r\nchecked>支付宝\r\n        <input type='radio' name='fs' id='fs2' value = '微信'>微信\r\n        <input type='radio' name='fs' id='fs3' value = '微信'>网银<br/><br/>\r\n        会员:\r\n<input type='checkbox' name='hy' id='hy'><br/><br/>\r\n        <input type='submit' name='sumbit' id='sumbit' value='提交'>\r\n        <input type='reset' name='reset' id='reset' value='重置'>\r\n        <input type='button' name='foxtble' id='foxtable' value='Foxtable主页'\r\nonclick='location=\"http://www.foxtable.com\"'>\r\n        </form>\r\n        ]]>.Value\r\n        e.WriteString(html)\r\n    Case \"accept.htm\"\r\n        Dim\r\nsb As New\r\nStringBuilder\r\n        sb.AppendLine(\"接收到的数据有:<br/><br/>\")\r\n        For Each\r\nkey As\r\nString In\r\ne.Values.Keys\r\n            sb.AppendLine(key\r\n& \":\"\r\n& e.Values(key)\r\n& \"<br/>\")\r\n        Next\r\n        e.WriteString(sb.ToString)\r\nEnd\r\nSelect\r\n\r\n这个代码生成了两个页面，第一个页面input.htm是一个表单，用于输入数据，这个表单展示了最常用的输入类型：\r\n\r\n用户单击提交按钮后，会将输入的数据提交到第二个页面accept.htm，该页面将收到的数据回写到客户端浏览器：\r\n\r\n建议你以后开发系统的时候，都先用上面的方法，实际测试一下表单所提交的数据内容和格式。\r\n\r\n需要注意的是，只有已经输入数据的输入框，以及已经勾选的radio和checbox，才会被提交到服务器，也就是说：\r\n\r\n1、没有数据的空白输入框，不会提交到服务器。\r\n2、没有勾选的radio和checkbox，也不会提交到服务器。\r\n\r\n## 在表单存储标记数据\r\n\r\n在表单插入标记数据\r\n\r\n\r\n**在表单插入标记数据**\r\n\r\n我们在生成表单的时候，可能需要在表单插入一些标记数据。\r\n\r\n例如你需要根据一个现有订单，来生成一个表单，那么就需要在生成的表单中存储主键值，这样用户编辑完成，将编辑结果提交到后台时，Foxtable才能知道用户编辑的是哪一个订单。\r\n\r\n我们可以增加一个隐藏的文本框，将这个文本框的值设置为主键，例如：\r\n\r\n<input name='identify' id='identify' value='1' **hidden**>\r\n\r\nvalue属性指定了这个文本框的默认值为1，加上hidden属性使得这个文本框在表单中处于隐藏状态，当用户提交表单数据到后台时，这个文本框的值会和其他输入框的值一起提交到后台\r\n，Foxtable就知道用户编辑的是主键为1的订单。\r\n\r\n我们通常不会专门介绍HTML的知识，但是这个方法对于我们今后设计应用系统很重要，所以用专门的一小节介绍一下。\r\n\r\n## 另一种数据提交方式\r\n\r\n另一种数据提交方式\r\n\r\n\r\n**另一种数据提交方式**\r\n\r\n除了前面介绍的通过表单提交数据，我们还可以通过URL地址提交数据，格式为：\r\n\r\nhttp://网页地址/?键1=值1&键2=值2&键3=值3\r\n\r\n**示例**\r\n\r\n将HttpRequest事件代码设置为：\r\n\r\nSelect\r\nCase e.Path\r\n     Case \"test.htm\"\r\n        Dim\r\nsb As New\r\nStringBuilder\r\n        sb.AppendLine(\"接收到的数据有:<br/><br/>\")\r\n        For Each\r\nkey As\r\nString In\r\ne.Values.Keys\r\n            sb.AppendLine(key\r\n& \":\"\r\n& e.Values(key)\r\n& \"<br/>\")\r\n        Next\r\n        e.WriteString(sb.ToString)\r\nEnd\r\nSelect\r\n\r\n然后在浏览器中输入地址：\r\n\r\nhttp://127.0.0.01/test.htm?product=foxtable&price=4688&count=2\r\n\r\n可以看到Foxtable已经正确提取出URL网址中包括的数据：\r\n\r\n这种数据提交方式，通常称为get方式，通过表单提交的方式，通常称为post方式。\r\n\r\n当然你可以将表单的method属性设置为get,这样表单也可以采用get方式提交数据，但是没有必要，因为post方式更安全、更方便，一次可以提交更多的数据。\r\n\r\n但是get方式也有自己的优势，就是轻量级，通常用于提交一些事先约定好的、不需要用户输入的数据。\r\n\r\n**混合Get和Post方式**\r\n\r\n其实Get和Post两种方式是可以混合使用的，例如我们将HttpRequest事件代码设置为：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"input.htm\"\r\n        Dim sb\r\nAs New\r\nStringBuilder\r\n\r\nsb.AppendLine(\"<form\r\nenctype='multipart/form-data' action='accept.htm?id=1&page=10' method='post'\r\nid='form1'\r\nname='form1'>\")\r\n        sb.AppendLine(\"产品:\r\n<input name='cp' id='cp'><br/><br/>\")\r\n        sb.AppendLine(\"客户:\r\n<input name='kh' id='kh'><br/><br/>\")\r\n        sb.AppendLine(\"数量:\r\n<input type='number' name='sl' id='sl'><br/><br/>\")\r\n        sb.AppendLine(\"日期:\r\n<input type='date' name='rq' id='rq'><br/><br/>\")\r\n        sb.AppendLine(\"<input\r\ntype='submit' name='sumbit' id='sumbit' value='提交'>\")\r\n        sb.AppendLine(\"<input\r\ntype='reset' name='reset' id='reset' value='重置'>\")\r\n         sb.AppendLine(\"</form>\")\r\n        e.WriteString(sb.ToString)\r\n    Case \"accept.htm\"\r\n        Dim sb\r\nAs New\r\nStringBuilder\r\n        sb.AppendLine(\"接收到的数据有:<br/><br/>\")\r\n        For Each\r\nkey As\r\nString In\r\ne.Values.Keys\r\n            sb.AppendLine(key\r\n& \":\"\r\n& e.Values(key)\r\n& \"<br/>\")\r\n        Next\r\n        e.WriteString(sb.ToString)\r\nEnd\r\nSelect\r\n\r\n上述代码中，我们定义的表单的代码为：\r\n\r\n<form action='accept.htm?id=1&page=10' method='post'\r\nname='form1'>\r\n\r\n用户输入完成单击提交按钮后，会将输入结果提交到地址\"accept.htm?id=1&page=10\"，这个地址包括id和page两个值。\r\n\r\n所以当用户按下图所示输入数据：\r\n\r\n然后单击提交按钮，提交到后台后，显示的内容会包括url地址中的内容，以及用户输入的内容：\r\n\r\n## Values、GetValues和PostValues\r\n\r\nValues、GetValues和PostValues\r\n\r\n\r\n**Values、GetValues和PostValues**\r\n\r\n前面已经说过，不管是通过GET方式提交，还是通过POST方式提交，客户端提交的所有数据都包括在Values字典中。\r\n\r\nHttpReqquest事件还有两个字典属性，他们是Values的子集：\r\n\r\nGetValues： 字典，仅包括通过GET方式提交的数据。\r\nPostValues：字典，仅包括通过POST方式提交的数据。\r\n\r\n例如我们将HttpRequest事件代码设置为：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"input.htm\"\r\n\r\nDim\r\nhtml\r\nAs\r\n\r\nString\r\n=\r\n<![CDATA[\r\n        <form enctype='multipart/form-data' action='accept.htm?id=1&page=10'\r\nmethod='post' id='form1' name='form1'>\r\n        产品:\r\n<input name='cp' id='cp'><br/><br/>\r\n        客户:\r\n<input name='kh' id='kh'><br/><br/>\r\n        数量:\r\n<input type='number' name='sl' id='sl'><br/><br/>\r\n        日期:\r\n<input type='date' name='rq' id='rq'><br/><br/>\r\n        <input type='submit' name='sumbit' id='sumbit' value='提交'>\r\n        <input type='reset' name='reset' id='reset' value='重置'>\r\n        </form>\r\n        ]]>.Value\r\n        e.WriteString(html)\r\n    Case \"accept.htm\"\r\n        Dim\r\nsb As New\r\nStringBuilder\r\n        sb.AppendLine(\"通过GET方式提交的数据:<br/><br/>\")\r\n        For Each\r\nkey As\r\nString In\r\ne.GetValues.Keys\r\n            sb.AppendLine(key\r\n& \":\"\r\n& e.GetValues(key)\r\n& \"<br/>\")\r\n        Next\r\n        sb.AppendLine(\"<br/><br/>通过POST方式提交的数据:<br/><br/>\")\r\n        For Each\r\nkey As\r\nString In\r\ne.PostValues.Keys\r\n            sb.AppendLine(key\r\n& \":\"\r\n& e.PostValues(key)\r\n& \"<br/>\")\r\n        Next\r\n        e.WriteString(sb.ToString)\r\nEnd\r\nSelect\r\n\r\n上述代码中，我们定义的表单的代码为：\r\n\r\n<form enctype='multipart/form-data' action='accept.htm?id=1&page=10'\r\nmethod='post' id='form1' name='form1'>\r\n\r\n用户输入完成单击提交按钮后，会将输入结果提交到地址\"accept.htm?id=1&page=10\"，这个地址包括id和page两个值。\r\n\r\n当用户按下图所示输入数据：\r\n\r\n然后单击提交按钮，提交到后台后，可以看到分别显示GET和POST方式提交的数据：\r\n\r\n## 简单的数据统计\r\n\r\n简单的数据统计\r\n\r\n\r\n**简单的数据统计**\r\n\r\n**本节任务**\r\n\r\n我们希望让用户通过下面的网页输入年月：\r\n\r\n单击“开始统计”，由Foxtable统计出该月各产品的销售数量，并显示在用户的浏览器上：\r\n\r\n**知识准备**\r\n\r\n用户是通过表单(form)输入数据的，这是我们本节要用到的一个表单：\r\n\r\n第1行代码为：\r\n\r\n<form action='showtj.htm' enctype='multipart/form-data'\r\nmethod='post' id='form1' name='form1'>\r\n\r\n表示用户单击确定按钮后，将结果提交到后台的showtj.htm网页处理，这个表单的名称为form1，数据提交方式为post\r\n\r\n再看第3行代码：\r\n\r\n年: <input type='number' name='year' id='year' min='2000' max='2018'><br/><br/>\r\n\r\n表示这是一个数值输入框，输入值的范围在2000到2018之间，这个输入框的名称（name）为\"year\"，我们必须给输入框指定name属性，因为后台的Foxtable要通过name属性取得输入值。\r\n\r\n服务端的HttpRequest事件通过e参数PostValues获取用户输入的值，这是一个字典，键为输入框的name属性，值就是用户所输入的值，例如在HttpRequest事件\r\n获取用户通过上述表单输入的年份的代码为：\r\n\r\nDim\r\ny As Integer\r\n= 0\r\n'将输入的年转换为整数，因为有的浏览器数值输入框也能输入字符!\r\nInteger.TryParse(e.PostValues(\"year\"),\r\ny)\r\nIf y = 0\r\nThen\r\n     e.WriteString(\"请输入年!\")\r\nEnd\r\nIf\r\n\r\n**完整代码**\r\n\r\n下面是HttpRequest事件的完整代码，代码不长，却完成了生成输入页面、获取用户输入\r\n内容、统计数据，以及呈现统计结果等四大任务：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"saletj.htm\"\r\n        Dim sb\r\nAs New\r\nStringBuilder\r\n\r\nsb.AppendLine(\"<form\r\nenctype='multipart/form-data' action='showtj.htm' method='post'\r\nid='form1'\r\nname='form1'>\")\r\n        sb.AppendLine(\"请输入要统计的年月:</br></br>\")\r\n        sb.AppendLine(\"年:\r\n<input type='number' name='year' id='year' min='1999' max='2018'><br/><br/>\")\r\n        sb.AppendLine(\"月:\r\n<input type='number' name='month' id='month' min='1' max='12'><br/><br/>\")\r\n        sb.AppendLine(\"<input\r\nType='submit' name='Sumbit' id='Sumbit' value='开始统计'>\")\r\n        sb.AppendLine(\"</form>\")\r\n        e.WriteString(sb.ToString)\r\n    Case \"showtj.htm\"\r\n\r\nDim y\r\nAs Integer =\r\n0\r\n        Dim m\r\nAs Integer =\r\n0\r\n\r\nInteger.TryParse(e.PostValues(\"year\"),\r\ny)\r\n'获取用户输入的\r\n年\r\n        Integer.TryParse(e.PostValues(\"month\"),\r\nm)\r\n'获取用户输入的月\r\n        If y\r\n= 0 OrElse\r\nm = 0\r\nThen\r\n            e.WriteString(\"请输入正确的年月!\")\r\n        Else\r\n            '根据输入的年月,统计各产品的销售数量\r\n            Dim g\r\nAs New\r\nSQLGroupTableBuilder(\"统计表1\",\r\n\"订单\")\r\n'这里使用后台统计,如果数据已经全部加载可以直接用GroupTableBuilder\r\n\r\n'g.ConnectionName\r\n= \"数据源\" '外部数据表的话要指定数据源名称\r\n            g.Groups.AddDef(\"产品\")\r\n            g.Totals.AddDef(\"数量\")\r\n            g.VerticalTotal\r\n= True\r\n            g.Filter\r\n= \"Year(日期)\r\n= \"\r\n& y\r\n&\r\n\" And Month(日期)\r\n= \"\r\n& m\r\n'后台统计才可以使用这种表达式的哦\r\n            Dim dt\r\nAs DataTable\r\n= g.Build(False)\r\n\r\n\r\n'将统计结果输出到用户浏览器\r\n            Dim sb\r\nAs New\r\nStringBuilder\r\n            sb.AppendLine(y\r\n& \"年\"\r\n& m\r\n& \"月各产品销售数量: <br/><br/>\")\r\n            For Each\r\ndr As\r\nDataRow In\r\ndt.DataRows\r\n                sb.AppendLine(dr(\"产品\")\r\n& \":\"\r\n& dr(\"数量\")\r\n& \"<br/>\")\r\n            Next\r\n            e.WriteString(sb.ToString)\r\n        End\r\nIf\r\nEnd\r\nSelect\r\n\r\n## 可能会出现性能问题\r\n\r\n可能会出现性能问题\r\n\r\n\r\n**可能会出现性能问题**\r\n\r\n一般用户可以忽略本节内容。\r\n\r\n上一节我们完成了一个简单的数据交互任务：\r\n让用户通过网页输入年月，后台根据用户输入的年月，统计出该月各产品的销售数量，并显示在用户浏览器上。\r\n\r\n不管是输入页面\"saletj.htm\"，还是显示统计结果的页面\"showtj.htm\"，其实都是不存在的，是后台动态生成的，是虚的。\r\n\r\n动态生成网页的优势是灵活，但是也有一个劣势，就是性能。\r\n\r\n以上一节的例子为例，每次统计的耗时和数据量是成正比的，如果用户每次访问都重新进行一次统计，且用户量和数据量都比较大，那么就必须考虑服务器的负担问题了。\r\n\r\n要减轻服务器的负担，我们可以这样考虑：\r\n\r\n1、本月的销售数据在不断变化中，适合每次访问都重新统计，而之前月份的销售数据已经固定，没有必要重复统计。\r\n\r\n2、所以我们可以将之前月份的统计结果保存为一个网页，这样以后访问时，无需再次进行统计\r\n，直接发送已经生成的网页即可。\r\n\r\n3、至于本月销售数据，当然每次都重新统计是最好的，但实际上用户统计本月数据的概率是最大的，每次重新统计，也可能导致服务器负担过重。\r\n\r\n4、所以对于本月统计，我们一样可以将统计结果保存为一个网页，用户每次访问时，判断这个网页的生成时间，如果没有超过1小时，就直接发送这个网页，否则重新进行统计。\r\n\r\n5、为方便管理，可以在\"d:\\web\"目录下，建立一个子目录\"temp\"，用于存放这些临时生成的网页。\r\n\r\n这是修改后的HttpRequest事件的代码，要正常运行这段代码，请在\"d:\\web\"目录下，建立一个子目录\"temp\"：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"saletj.htm\"\r\n        Dim sb\r\nAs New\r\nStringBuilder\r\n\r\nsb.AppendLine(\"<form\r\nenctype='multipart/form-data' action='showtj.htm' method='post'\r\nid='form1'\r\nname='form1'>\")\r\n        sb.AppendLine(\"请输入要统计的年月:</br></br>\")\r\n        sb.AppendLine(\"年:\r\n<input type='number' name='year' id='year' min='1999' max='2018'><br/><br/>\")\r\n        sb.AppendLine(\"月:\r\n<input type='number' name='month' id='month' min='1' max='12'><br/><br/>\")\r\n        sb.AppendLine(\"<input\r\nType='submit' name='Sumbit' id='Sumbit' value='开始统计'>\")\r\n        sb.AppendLine(\"</form>\")\r\n        e.WriteString(sb.ToString)\r\n    Case \"showtj.htm\"\r\n        Dim y\r\nAs Integer =\r\n0\r\n        Dim m\r\nAs Integer =\r\n0\r\n\r\nInteger.TryParse(e.PostValues(\"year\"),\r\ny)'获取用户输入的年\r\n        Integer.TryParse(e.PostValues(\"month\"),\r\nm)\r\n'获取用户输入的月\r\n        If y\r\n= 0 OrElse\r\nm = 0\r\nThen\r\n            e.WriteString(\"请输入正确的年月!\")\r\n        Else\r\n            Dim\r\nfl As String\r\n= \"d:\\web\\temp\\\" &\r\ny &\r\nm &\r\n\".htm\"\r\n'合成文件名\r\n            If FileSys.FileExists(fl)\r\nThen\r\n'如果文件存在\r\n                Dim\r\nifo As new\r\nFileInfo(fl)\r\n\r\n'如果不是本月,或者文件最近一次修改时间在1个小时内,则发送文件后返回\r\n\r\nIf\r\ny <> Date.Today.Year\r\nOrElse m <>\r\nDate.Today.Month\r\nOrElse (Date.Now\r\n- Ifo.LastWriteTime).TotalHours\r\n< 1\r\n                    e.WriteFile(fl)\r\n                    Return\r\n                End\r\nIf\r\n            End\r\nIf\r\n            '根据输入的年月,统计各产品的销售数量\r\n            Dim g\r\nAs New\r\nSQLGroupTableBuilder(\"统计表1\",\r\n\"订单\")\r\n'这里使用后台统计,如果数据已经全部加载可以直接用GroupTableBuilder\r\n\r\n\r\n'g.ConnectionName = \"数据源\"\r\n'外部数据表的话要指定数据源名称\r\n            g.Groups.AddDef(\"产品\")\r\n            g.Totals.AddDef(\"数量\")\r\n            g.VerticalTotal\r\n= True\r\n            g.Filter\r\n= \"Year(日期)\r\n= \"\r\n& y\r\n&\r\n\" And Month(日期)\r\n= \"\r\n& m\r\n'后台统计才可以使用这种表达式的哦\r\n            Dim dt\r\nAs DataTable\r\n= g.Build(False)\r\n\r\n'统计数据,将结果保存为网页,然后发送这个网页\r\n            Dim sb\r\nAs New\r\nStringBuilder\r\n            sb.AppendLine(y\r\n& \"年\"\r\n& m\r\n& \"月各产品销售数量: <br/><br/>\")\r\n            For Each\r\ndr As\r\nDataRow In\r\ndt.DataRows\r\n                sb.AppendLine(dr(\"产品\")\r\n& \":\"\r\n& dr(\"数量\")\r\n& \"<br/>\")\r\n            Next\r\n            sb.AppendLine(\"<br/>\")\r\n            sb.AppendLine(\"统计时间:\"\r\n& Date.Now)\r\n'在网页显示统计时间,提醒用户这可能不是最新的结果\r\n            FileSys.WriteAllText(fl,\r\nsb.ToString,\r\nFalse, Encoding.UTF8)\r\n'记得用UTF8格式保存网页\r\n            e.WriteFile(fl)\r\n'发送保存的网页\r\n        End\r\nIf\r\nEnd\r\nSelect\r\n\r\n如果之前月份的销售数据也有调整的可能，导致之前月份的统计结果也会变化，只是变化的概率和频率不高，那么你可以调整代码，检测之前月份统计文件的创建时间，如果超过1天\r\n（具体时长根据需要调整），就重新统计。\r\n\r\n## 一个页面完成一个任务\r\n\r\n一个页面完成一个任务\r\n\r\n\r\n**一个页面完成一个任务**\r\n\r\n上一节的统计任务，我们使用了两个页面，页面\"saletj.htm\"用于输入统计年月，页面\"showtj.htm\"用于显示统计结果。\r\n\r\n如果你愿意的话，也可以只用一个页面\"saletj.htm\"来完成这个统计任务，从代码维护角度而言，我更愿意用一个页面处理一个任务。\r\n\r\n**知识准备**\r\n\r\n我们已经知道，HttpRequest事件有个e参数PostValues，用于获取用户通过表单提交的数据。\r\n我们可以用一个隐藏的输入框，在这个输入框填入约定的数据，HttpRequest根据这个输入框的值，来执行相应的操作。\r\n\r\n例如：\r\n\r\n<input name='tj' id='tj' value='按年月统计' hidden>\"\r\n\r\n这里创建了一个名为\"tj\"的输入框，默认值是\"按年月统计\"，这个输入框加上了hidden属性，所以在页面中并不会显示，用户不会感觉有什么变化。\r\n\r\n在HttpRequest通过下面的代码，判断如果提交的页面名为tj的输入框，且他的值是\"按年月统计\"，那么就进行统计并显示统计结果，否则生成年月输入页面：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"saletj.htm\"\r\n        If\r\ne.PostValues.ContainsKey(\"tj\") AndAlso\r\ne.PostValues(\"tj\") = \"按年月统计\"\r\nThen\r\n              '统计数据，并显示统计结果\r\n\r\n        Else\r\n              '生成年月录入界面\r\n        End\r\nIf\r\nEnd\r\nSelect\r\n\r\n我们也可以用更简单的方式来区分两个页面：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"saletj.htm\"\r\n        If\r\ne.PostValues.Count\r\n> 0\r\nThen\r\n\r\n'统计数据，并显示统计结果\r\n\r\n        Else\r\n              '生成年月录入界面\r\n        End\r\nIf\r\nEnd\r\nSelect\r\n\r\n原理很简单，如果是直接访问saletj.htm，那么PostValues为空，其Count属性等于0，如果是通过表单的“开始统计”按钮访问saletj.htm，那么PostValues就会包括用户输入的值，其Count属性就会大于0。\r\n\r\n最后别忘记修改表单定义语句，该语句之前是：\r\n\r\n<form action='showtj.htm' enctype='multipart/form-data'\r\nmethod='post' id='form1' name='form1'>\r\n\r\n我们需要改为：\r\n\r\n<form action='saletj.htm' enctype='multipart/form-data' method='post' id='form1'\r\nname='form1'>\r\n\r\n**完整的代码**\r\n\r\n下面是HttpRequest事件的完整代码，只使用一个页面完成和上一节完全相同的任务：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"saletj.htm\"\r\n        If\r\n\r\ne.PostValues.ContainsKey(\"tj\") AndAlso e.PostValues(\"tj\") = \"按年月统计\"\r\nThen\r\n            Dim\r\ny As\r\nInteger = 0\r\n            Dim\r\nm As\r\nInteger = 0\r\n\r\nInteger.TryParse(e.PostValues(\"year\"),\r\ny)\r\n'获取用户输入的年\r\n\r\nInteger.TryParse(e.PostValues(\"month\"),\r\nm)\r\n'获取用户输入的月\r\n            If\r\ny = 0\r\nOrElse m = 0\r\nThen\r\n                e.WriteString(\"请输入正确的年月!\")\r\n            Else\r\n                Dim\r\nfl As\r\nString =\r\n\"d:\\web\\temp\\\" &\r\ny &\r\nm &\r\n\".htm\"\r\n'合成文件名\r\n                If\r\nFileSys.FileExists(fl)\r\nThen\r\n'如果文件存在\r\n                    Dim\r\nifo As\r\nnew FileInfo(fl)\r\n\r\n\r\n\r\n'如果不是本月,或者文件创建时间在1个小时内,则发送文件后返回\r\n\r\nIf\r\ny <> Date.Today.Year\r\nOrElse m <>\r\nDate.Today.Month\r\nOrElse (Date.Now\r\n- Ifo.LastWriteTime).TotalHours\r\n< 1\r\n                        e.WriteFile(fl)\r\n                        Return\r\n                    End\r\nIf\r\n                End\r\nIf\r\n\r\n '根据输入的年月,统计各产品的销售数量\r\n                Dim\r\ng As New\r\nSQLGroupTableBuilder(\"统计表1\",\r\n\"订单\")\r\n'这里使用后台统计,如果数据已经全部加载可以直接用GroupTableBuilder\r\n\r\n'g.ConnectionName = \"数据源\"\r\n'外部数据表的话要指定数据源名称\r\n                g.Groups.AddDef(\"产品\")\r\n                g.Totals.AddDef(\"数量\")\r\n                g.VerticalTotal\r\n= True\r\n                g.Filter\r\n=  \"Year(日期)\r\n= \"\r\n& y\r\n&\r\n\" And Month(日期)\r\n= \"\r\n& m\r\n'后台统计才可以使用这种表达式的哦\r\n                Dim\r\ndt As\r\nDataTable = g.Build(False)\r\n\r\n\r\n\r\n'统计数据,将结果保存为网页,然后发送这个网页\r\n                Dim\r\nsb As New\r\nStringBuilder\r\n                sb.AppendLine(y\r\n& \"年\"\r\n& m\r\n& \"月各产品销售数量: <br/><br/>\")\r\n                For\r\nEach dr As\r\nDataRow In\r\ndt.DataRows\r\n                    sb.AppendLine(dr(\"产品\")\r\n& \":\"\r\n& dr(\"数量\")\r\n& \"<br/>\")\r\n                Next\r\n                sb.AppendLine(\"<br/>\")\r\n                sb.AppendLine(\"统计时间:\"\r\n& Date.Now)\r\n'在网页显示统计时间,提醒用户这可能不是最新的结果\r\n                FileSys.WriteAllText(fl,\r\nsb.ToString,\r\nFalse, Encoding.UTF8)\r\n'记得用UTF8格式保存网页\r\n                e.WriteFile(fl)\r\n'发送保存的网页\r\n\r\nEnd\r\nIf\r\n        Else\r\n            Dim\r\nsb As\r\nNew StringBuilder\r\n\r\nsb.AppendLine(\"<form\r\nenctype='multipart/form-data' action='saletj.htm' method='post'\r\nid='form1'\r\nname='form1'>\")\r\n            sb.AppendLine(\"请输入要统计的年月:</br></br>\")\r\n            sb.AppendLine(\"<input\r\nname='tj' id='tj' value='按年月统计'\r\nhidden>\")\r\n'这个输入框不会显示，仅用于标记\r\n            sb.AppendLine(\"年:\r\n<input type='number' name='year' id='year' min='1999' max='2018'><br/><br/>\")\r\n            sb.AppendLine(\"月:\r\n<input type='number' name='month' id='month' min='1' max='12'><br/><br/>\")\r\n            sb.AppendLine(\"<input\r\nType='submit' name='Sumbit' id='Sumbit' value='开始统计'>\")\r\n            sb.AppendLine(\"</form>\")\r\n            e.WriteString(sb.ToString)\r\n        End\r\n If\r\nEnd\r\nSelect\r\n\r\n## 简化HttpRequest事件代码\r\n\r\n简化HttpRequest事件代码\r\n\r\n\r\n简化HttpRequest事件代码\r\n\r\n到上一节位置，我们的代码已经有点长了，这只是一个页面而已哦。\r\n随着页面的增加，代码会十倍甚至百倍成长，而Foxtable的代码编辑器并不适合编辑超长的代码，所以最好使用自定义函数，一个页面做成一个函数，方便管理。\r\n关于自定义函数，大家可以参考：[自定义函数](http://www.foxtable.com/webhelp/scr/1486.htm)\r\n\r\n用上一节的任务作为例子，我们采用自定义函数来实现：\r\n\r\n**1、定义函数**\r\n\r\n首先定义一个名为saletj的自定义函数，代码为：\r\n\r\nDim\r\ne As\r\nRequestEventArgs\r\n= args(0)\r\nIf\r\ne.PostValues.ContainsKey(\"tj\")\r\nAndAlso e.PostValues(\"tj\")\r\n= \"按年月统计\"\r\nThen\r\n\r\n\r\nDim y\r\nAs Integer\r\n= 0\r\n    Dim\r\nm As\r\nInteger = 0\r\n    Integer.TryParse(e.PostValues(\"year\"),\r\ny)\r\n'获取用户输入的年\r\n    Integer.TryParse(e.PostValues(\"month\"),\r\nm)\r\n'获取用户输入的月\r\n    If\r\ny = 0 OrElse\r\nm = 0 Then\r\n        e.WriteString(\"请输入正确的年月!\")\r\n    Else\r\n        Dim fl\r\nAs String =\r\n\"d:\\web\\temp\\\" &\r\ny &\r\nm &\r\n\".htm\"\r\n'合成文件名\r\n        If FileSys.FileExists(fl)\r\nThen\r\n'如果文件存在\r\n            Dim ifo\r\nAs new\r\nFileInfo(fl)\r\n\r\n'如果不是本月,或者文件创建时间在1个小时内,则发送文件后返回\r\n\r\nIf\r\ny <> Date.Today.Year\r\nOrElse m <>\r\nDate.Today.Month\r\nOrElse (Date.Now\r\n- Ifo.LastWriteTime).TotalHours\r\n< 1\r\n                e.WriteFile(fl)\r\n                Return\r\n\"\"\r\n            End\r\nIf\r\n        End\r\nIf\r\n        '根据输入的年月,统计各产品的销售数量\r\n        Dim g\r\nAs New\r\nSQLGroupTableBuilder(\"统计表1\",\r\n\"订单\")\r\n'这里使用后台统计,如果数据已经全部加载可以直接用GroupTableBuilder\r\n\r\n'g.ConnectionName = \"数据源\"\r\n'外部数据表的话要指定数据源名称\r\n        g.Groups.AddDef(\"产品\")\r\n        g.Totals.AddDef(\"数量\")\r\n        g.VerticalTotal\r\n= True\r\n        g.Filter\r\n= \"Year(日期)\r\n= \"\r\n& y\r\n&\r\n\" And Month(日期)\r\n= \"\r\n& m\r\n'后台统计才可以使用这种表达式的哦\r\n        Dim dt\r\nAs DataTable\r\n= g.Build(False)\r\n\r\n'统计数据,将结果保存为网页,然后发送这个网页\r\n        Dim sb\r\nAs New\r\nStringBuilder\r\n        sb.AppendLine(y\r\n& \"年\"\r\n& m\r\n& \"月各产品销售数量: <br/><br/>\")\r\n        For Each\r\ndr As\r\nDataRow In\r\ndt.DataRows\r\n            sb.AppendLine(dr(\"产品\")\r\n& \":\"\r\n& dr(\"数量\")\r\n& \"<br/>\")\r\n        Next\r\n        sb.AppendLine(\"<br/>\")\r\n        sb.AppendLine(\"统计时间:\"\r\n& Date.Now)\r\n'在网页显示统计时间,提醒用户这可能不是最新的结果\r\n        FileSys.WriteAllText(fl,\r\nsb.ToString,\r\nFalse, Encoding.UTF8)\r\n'记得用UTF8格式保存网页\r\n        e.WriteFile(fl)\r\n'发送保存的网页\r\n\r\nEnd\r\nIf\r\nElse\r\n    Dim sb\r\nAs New\r\nStringBuilder\r\n\r\nsb.AppendLine(\"<form\r\nenctype='multipart/form-data' action='saletj.htm' method='post'\r\nid='form1'\r\nname='form1'>\")\r\n    sb.AppendLine(\"请输入要统计的年月:</br></br>\")\r\n    sb.AppendLine(\"<input\r\nname='tj' id='tj' value='按年月统计'\r\nhidden>\")\r\n    sb.AppendLine(\"年:\r\n<input type='number' name='year' id='year' min='1999' max='2018'><br/><br/>\")\r\n    sb.AppendLine(\"月:\r\n<input type='number' name='month' id='month' min='1' max='12'><br/><br/>\")\r\n    sb.AppendLine(\"<input\r\nType='submit' name='Sumbit' id='Sumbit' value='开始统计'>\")\r\n    sb.AppendLine(\"</form>\")\r\n    e.WriteString(sb.ToString)\r\nEnd\r\nIf\r\n\r\n上面的代码和上一节相比，\r\n变化非常小，只有两处，首先在第一行位置插入了一行代码：\r\n\r\nDim\r\ne As\r\nRequestEventArgs\r\n= args(0)\r\n\r\nRequestEventArgs是HttpRequest事件的e参数类型。\r\n\r\n其次是将代码中的\r\n\r\nReturn\r\n\r\n改为了\r\n\r\nReturn\r\n\"\"\r\n\r\n2、修改HttpRequest事件代码\r\n\r\n接下来将HttpRequest事件的代码改为：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"saletj.htm\"\r\n\r\nFunctions.Execute(\"saletj\",e)\r\nEnd\r\nSelect\r\n\r\n由于主要的工作已经转移到自定义函数中，所以HttpRequest变得很简洁。\r\n\r\n为便于讲述，接下来的例子并不会采用自定义函数，但是实际开发的时候，建议大一点的系统，都应该采用自定义函数，如果所有代码都写在HttpRequest事件中，你将痛苦不堪。\r\n\r\n## 通用HttpRqeust事件头\r\n\r\n通用HttpRqeust事件头\r\n\r\n\r\n**通用HttpRqeust事件头**\r\n\r\n建议实际开发的时候，建议在HttpRqeust事件的开始位置都加上一段代码，用于发送已经存在的常见文件：\r\n\r\n'通用事件头,用于发送已经存在的常见文件\r\nDim\r\nfl As\r\nString =\r\n\"d:\\web\\\" &\r\ne.path\r\nIf\r\nfilesys.FileExists(fl)\r\n    Dim idx\r\nAs Integer\r\n= fl.LastIndexOf(\".\")\r\n    Dim ext\r\nAs String\r\n= fl.SubString(idx)\r\n    Select Case\r\next\r\n        Case\r\n\".jpg\",\".gif\",\".png\",\".bmp\",\".wmf\",\".js\",\".css\"\r\n,\".html\",\".htm\",\".zip\",\".rar\"\r\n\r\n            e.WriteFile(fl)\r\n            Return\r\n'这里必须返回\r\n    End\r\nSelect\r\nEnd\r\nIf\r\n'以下是动态生成网页的代码\r\nSelect\r\nCase e.Path\r\n\r\n    Case \"addnew.htm\"\r\n         '生成新增页面\r\n    Case \"tongji.htm\"\r\n         '生成统计页面\r\n    Case \"order.htm\"\r\n         '生成订购页面\r\n    Case\r\nElse\r\n       e.WriteString(\"糟糕,文件未找到！\")\r\nEnd\r\nSelect\r\n\r\n为了让大家专注与每一节要讲述的问题，帮助文件中很多例子是不会加上事件头的。\r\n\r\n有的时候，文件即使已经存在，也可能需要重新生成，参考：[可能出现的性能问题](0023.htm)\r\n\r\n对于这种情况，我们可以进行一些判断即可，排除这些特殊页面：\r\n\r\n'通用事件头,用于发送已经存在的常见文件\r\nSelect\r\nCase e.Path\r\n    Case \"saletj.htm\",\"showtj.htm\"\r\n'排除两个页面,由后面的代码负责处理\r\n    Case Else\r\n'其他页面或文件如果已经存在,直接发送\r\n\r\nDim\r\nfl As\r\nString =\r\n\"d:\\web\\\" &\r\ne.path\r\n        If\r\nfilesys.FileExists(fl)\r\n            Dim idx\r\nAs Integer\r\n= fl.LastIndexOf(\".\")\r\n            Dim ext\r\nAs String\r\n= fl.SubString(idx)\r\n            Select\r\nCase ext\r\n                Case\r\n\".jpg\",\".gif\",\".png\",\".bmp\",\".wmf\",\".js\",\".css\"\r\n,\".html\",\".htm\",\".zip\",\".rar\"\r\n                    e.WriteFile(fl)\r\n                    Return\r\n'这里必须返回\r\n            End\r\nSelect\r\n        End\r\nIf\r\nEnd\r\nSelect\r\n'以下是动态生成网页的代码\r\nSelect\r\nCase e.Path\r\n    Case \"addnew.htm\"\r\n        '生成新增页面\r\n    Case \"saletj.htm\"\r\n        '生成统计页面\r\n    Case \"showtj.htm\"\r\n        '生成统计结果显示页面\r\n    Case \"order.htm\"\r\n\r\n'生成订购页面\r\n    Case\r\nElse\r\n       e.WriteString(\"糟糕,文件未找到！\")\r\nEnd\r\nSelect\r\n\r\n有一个问题需要特别留意：\r\n\r\n以上面的代码为例，如果你在\"d:\\web\"目录下保存了一个\"addnew.htm\"网页文件，那么系统将直接发送这个文件，而不会执行后面动态生成\"addnew.htm\"的代码。\r\n如果某次你修改代码后，生成的网页始终没有变化，一般都是这个原因造成的。\r\n\r\n## 一个简单的录入界面\r\n\r\n一个简单的录入界面\r\n\r\n\r\n**一个简单的录入界面**\r\n\r\n后台有一个订单表，结构如下：\r\n\r\n现在希望设计一个网页用于输入新的订单：\r\n\r\n用户输入数据，单击确定后，能在后台的表新增订单，并显示如下页面，让用户选择是否继续增加订单：\r\n\r\n看似复杂，实际上HttpRequet事件的代码很简单，望大家仔细体会：\r\n\r\nSelect\r\nCase e.Path\r\n    Case\r\n\"AddNew.htm\"\r\n        If e.PostValues.Count\r\n= 0 Then\r\n            Dim\r\nsb As New\r\nStringBuilder\r\n\r\nsb.AppendLine(\"<form\r\naction='AddNew.htm' enctype='multipart/form-data' method='post'\r\nid='form1'\r\nname='form1'>\")\r\n            sb.AppendLine(\"产品:\r\n<input name='cp' id='cp'><br/><br/>\")\r\n            sb.AppendLine(\"客户:\r\n<input name='kh' id='kh'><br/><br/>\")\r\n            sb.AppendLine(\"雇员:\r\n<input name='gy' id='gy'><br/><br/>\")\r\n            sb.AppendLine(\"单价:\r\n<input type='number' name='dj' id='dj'><br/><br/>\")\r\n            sb.AppendLine(\"折扣:\r\n<input type='number' name='zk' id='zk' min='0' max='0.15' step='0.01'><br/><br/>\")\r\n            sb.AppendLine(\"数量:\r\n<input type='number' name='sl' id='sl'><br/><br/>\")\r\n            sb.AppendLine(\"日期:\r\n<input type='date' name='rq' id='rq'><br/><br/>\")\r\n            sb.AppendLine(\"<input\r\nType='submit' name='Sumbit' id='Sumbit' value='确定'>\")\r\n            sb.AppendLine(\"</form>\")\r\n            e.WriteString(sb.ToString)\r\n        Else\r\n            Dim\r\ndr As DataRow\r\n= DataTables(\"订单\").SQLAddNew()\r\n            Dim inms()\r\nAs String =\r\n{\"cp\",\"kh\",\"gy\",\"dj\",\"zk\",\"sl\",\"rq\"} '输入框名称数组\r\n\r\n\r\n            Dim\r\ncnms() As\r\nString = {\"产品\",\"客户\",\"雇员\",\"单价\",\"折扣\",\"数量\",\"日期\"}\r\n'列名数组,注意列名和输入框必须一一对应,位置不能错乱\r\n\r\nFor\r\ni As\r\nInteger = 0\r\nTo inms.Length\r\n-1\r\n                dr(cnms(i))\r\n= e.PostValues(Inms(i))\r\n\r\n            Next\r\n            dr.Save()\r\n'用SQLAddNew增加的行,必须保存一下,否则会被丢弃.\r\n\r\n\r\n\r\n            Dim sb\r\nAs New\r\nStringBuilder\r\n            sb.AppendLine(\"增加订单成功!\r\n<br/><br/>\")\r\n\r\n            sb.AppendLine(\"<a\r\nhref='AddNew.htm'>继续增加</a>\")\r\n\r\n            e.WriteString(sb.ToString)\r\n\r\nEnd\r\nIf\r\nEnd\r\nSelect\r\n\r\n提示：\r\n\r\n1、这里用SQLAddNew直接在后台增加行，SQLAddNew是Foxtable\r\n2017新增加的一个方法，适合加载结构但不加载数据的表，如果希望服务端能即时显示用户通过网页增加的行，请改用AddNew，二者效率差不多。\r\n2、如果用户量很大，需要更高的效率，建议采用SQL语句插入行，需要注意的是，只有外部数据源才支持用SQL语句插入行。\r\n\r\n## 文件的上传与接收\r\n\r\n文件的上传与接收\r\n\r\n\r\n**文件的上传与接收**\r\n\r\n用户可以通过表单上传文件。\r\n\r\n**表单准备**\r\n\r\n如果表单要包括文件上传组件，在定义表单的时候，要将enctype设置为\"multipart/form-data\"，例如：\r\n\r\n<form enctype='multipart/form-data' action='accept.htm'\r\nmethod='post' id='form1' name='form1'>\r\n</form>\r\n\r\n否则，HttpServer将无法收到用户上传的文件。\r\n\r\n在表单插入一个type为file的input元素，即可实现文件上传，例如：\r\n\r\n<input type='file' name='up1' id='up1'>\r\n\r\n如果要允许用户选择多个文件上传，加上multiple属性即可，例如\r\n\r\n<input type='file' name='up2' id='up2' multiple>\r\n\r\n下面是一个完整的文件上传表单：\r\n\r\n<form enctype='multipart/form-data' action='accept.htm' method='post' id='form1'\r\nname='form1'>\r\n单文件上传: <input type='file' name='up1' id='up1'><br/><br/>\r\n多文件上传: <input type='file' name='up2' id='up2' multiple><br/><br/>\r\n<input Type='submit' name='Sumbit' id='Sumbit' value='确定'>\r\n</form>\r\n\r\n接收文件\r\n\r\nHttpRequest的e参数有个Files属性，这是一个字典，键为文件上传组件的name属性，值是一个字符串集合，包括用户通过这个上传组件上传的所有文件名。\r\n\r\nHttpRequest的e参数有个SaveFile方法，用于保存接收到的文件，其语法为：\r\n\r\nSaveFile(Key,UploadFile,LocalFile)\r\n\r\n|  |  |\r\n| --- | --- |\r\n| Key | 文件上传组件的name属性 |\r\n| UploadFile | 用户上传的文件名称，不含路径。 |\r\n| LocalFile | 要保存到本地的文件名称，含路径 |\r\n\r\n**完整示例**\r\n\r\n将HttpRequest设置为以下代码：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"upload.htm\"\r\n        Dim sb\r\nAs New\r\nStringBuilder\r\n        sb.appendLine(\"<form\r\nenctype='multipart/form-data' action='accept.htm' method='post'\r\nid='form1' name='form1'>\")\r\n        sb.appendLine(\"单文件上传:\r\n<input type='file' name='up1' id='up1'><br/><br/>\")\r\n        sb.appendLine(\"多文件上传:\r\n<input type='file' name='up2' id='up2' multiple><br/><br/>\")\r\n        sb.appendLine(\"<input\r\nType='submit' name='Sumbit' id='Sumbit' value='确定'>\")\r\n        sb.appendLine(\"</form>\")\r\n        e.WriteString(sb.ToString)\r\n    Case \"accept.htm\"\r\n        Dim sb\r\nAs New\r\nStringBuilder\r\n        For Each\r\nkey As\r\nString In\r\ne.Files.Keys\r\n            sb.AppendLine(key\r\n&\r\n\"\r\n上传\"\r\n& e.Files(key).Count\r\n&\r\n\"个文件,分别是:</br>\")\r\n            For Each\r\nfl As\r\nString In\r\ne.Files(key)\r\n                sb.AppendLine(fl\r\n& \"<br>\")\r\n                e.SaveFile(key,fl,\"d:\\web\\uploadfiles\\\"\r\n& fl)\r\n'保存接收到的文件\r\n\r\n\r\nNext\r\n            sb.AppendLine(\"</br>\")\r\n        Next\r\n        sb.AppendLine(\"以上文件服务器已正确接收并保存!\")\r\n        e.WriteString(sb.ToString)\r\nEnd\r\nSelect\r\n\r\n上面的代码生成了两个页面，upload页面用于上传文件：\r\n\r\naccept.htm页面用于显示并保存用户上传的文件：\r\n\r\n## 避免覆盖同名文件\r\n\r\n避免覆盖同名文件\r\n\r\n\r\n**避免覆盖同名文件**\r\n\r\n我们在上一节设计的文件上传和接收示例中，如果多个用户上传了同名的文件，会出现覆盖的情况。\r\n\r\n为避免同名文件被覆盖，我们可以将HttpRequest事件代码改为：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"upload.htm\"\r\n        Dim\r\nsb As New\r\nStringBuilder\r\n        sb.appendLine(\"<form\r\nenctype='multipart/form-data' action='accept.htm' method='post'\r\nid='form1'\r\n\r\nname='form1'>\")\r\n        sb.appendLine(\"单文件上传:\r\n<input type='file' name='up1' id='up1'><br/><br/>\")\r\n        sb.appendLine(\"多文件上传:\r\n<input type='file' name='up2' id='up2' multiple><br/><br/>\")\r\n        sb.appendLine(\"<input\r\nType='submit' name='Sumbit' id='Sumbit' value='确定'>\")\r\n        sb.appendLine(\"</form>\")\r\n        e.WriteString(sb.ToString)\r\n    Case \"accept.htm\"\r\n        Dim\r\nsb As New\r\nStringBuilder\r\n        For\r\nEach key As\r\nString In\r\ne.Files.Keys\r\n            sb.AppendLine(key\r\n&\r\n\"\r\n上传\"\r\n& e.Files(key).Count\r\n&\r\n\"个文件,分别是:</br>\")\r\n            For Each\r\nfl As\r\nString In\r\ne.Files(key)\r\n                Dim\r\nNewName As\r\nString = fl\r\n            \n\nFile v2.0.5:references/06_扩展WeUI的功能.md\n\n# 扩展WeUI的功能\r\n\r\n\r\n# 扩展WeUI的功能\r\n\r\n扩展WeUI的功能\r\n\r\n\r\n**扩展WeUI的功能**\r\n\r\n如果你已经熟练掌握了WeUI，觉得还不够用，官方在Foxtable社区提供了一个ExWeUI，在WeUI的基础上提供了更多的功能，如：\r\n\r\n地址：\r\n\r\n<http://www.foxtable.com/bbs/dispbbs.asp?boardid=2&Id=150866>\n\nFile v2.0.5:references/07_客户端类.md\n\n# 客户端类\r\n\r\n\r\n## HttpClient\r\n\r\nHttpClient\r\n\r\n\r\n**HttpClient**\r\n\r\nB/S程序在一定程度上，也可以看作是C/S程序，只是此时客户端是一个浏览器而已。\r\n\r\n和HttpServer配合的客户端并非一定要浏览器，也可以是其它程序。\r\n\r\n我们可以在其它程序中编码模仿浏览器，向HttpServer发送访问请求，模拟提交Cookie和表单数据，以及上传文件，并接收从HttpServer返回的Cookie、数据\r\n和文件。\r\n\r\n如果你不是专业程序员，完成上述任务会有困难，所以我们为此提供了一个HttpClient类。\r\n\r\nHttpClient可以完成浏览器所有的工作，只不过这一切都是通过编码进行的，而且是非常简单的代码。\r\n\r\n在线用户数控制、独占式编辑、流程管理和不重复编号的生成等等，我们以前都是通过OpenQQ来实现，由于OpenQQ是封闭的，且有严谨的控制逻辑，所以使用起来会繁琐一些。\r\n\r\n所有这些都可以通过HttpClient实现，而且更简单，这是因为HttpClient和HttpServer是开放的，某种程度也可以认为是随意的\r\n，你甚至可以用很简单的代码完成一个数据中间件的编写工作。\r\n\r\n我并不打算将这些经典的示例用HttpServer+HttpClient重写一次，因为原理和逻辑都是一样的。\r\n\r\n**重要提示**\r\n\r\n1、HttpServer和HttpClient必须分别运行在不同的项目中，否则程序将会锁死.\r\n\r\n2、如果客户端项目和服务端项目在同一台电脑运行，二者都必须以Windows管理员身份运行，否则客户端项目将无法正常编译代码。\r\n\r\n3、切记以上两点。\r\n\r\n## 接收数据\r\n\r\n接收数据\r\n\r\n\r\n**接收数据**\r\n\r\nHttpClient的使用非常简单，只需两行代码即可从服务器获取数据。\r\n\r\n**一个例子**\r\n\r\n1、在服务端项目的HttpRequest事件中设置代码：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"gettime.htm\"\r\n\r\ne.WriteString(Date.now)\r\nEnd\r\nSelect\r\n\r\n2、在客户端项目通过下面的代码，即可获取服务器时间：\r\n\r\nDim\r\nhc As\r\nNew HttpClient(\"http://127.0.0.1/gettime.htm\")\r\nDim\r\ndt As\r\nDate = hc.GetData()\r\nMessageBox.Show(dt)\r\n\r\n过程很简单，首先是新建一个HttpClient，新建HttpClient的语法为：\r\n\r\nDim 变量名 As New\r\nHttpClient(网址)\r\n\r\n然后通过HtpClient的GetData方法将访问请求提交到服务器，并从服务器获取返回的数据。\r\n\r\n再次提示：服务端和客户端必须分开两个项目运行，否则Foxtable将被锁死。\r\n\r\n## 发送数据\r\n\r\n发送数据\r\n\r\n\r\n**发送数据**\r\n\r\nHttpClient可以直接将字符型数据发送到服务器，只需将其Content属性设为要发送的数据即可。\r\n\r\n一个例子\r\n\r\n1、在服务端项目的HttpRequest事件中设置代码：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"test.htm\"\r\n        If e.PlainText\r\n> \"\" Then\r\n            e.WriteString(\"服务器收到以下数据:\"\r\n& vbcrlf\r\n& e.PlainText)\r\n        Else\r\n            e.WriteString(\"服务器未收到数据\")\r\n\r\nEnd\r\nIf\r\nEnd\r\nSelect\r\n\r\n2、在客户端项目的命令窗口执行下面的代码，会提示服务器收到的数据：\r\n\r\nDim\r\nhc As\r\nNew HttpClient(\"http://127.0.0.1/test.htm\")\r\nhc.Timeout\r\n= 5\r\n'设置超时为5秒\r\nhc.Content\r\n= \"我是Foxtable\"\r\nDim\r\nret As\r\nString = hc.GetData()\r\nMessageBox.Show(ret)\r\n\r\n提示HttpRequest会将收到的纯文本数据保存在e参数PlainText中。\r\n\r\n## 收发Cookie\r\n\r\n收发Cookie\r\n\r\n\r\n**收发Cookie**\r\n\r\nHttpClient有两个和Cookie相关的字典属性，分别是：\r\n\r\n* sCookies\r\n  要发送到服务器的所有Cookie。\r\n* rCookies\r\n  从服务器返回的所有Cookie。\r\n\r\n**一个例子**\r\n\r\n1、在服务端项目的HttpRequest事件中设置代码：\r\n\r\nDim\r\nVerified As\r\nBoolean\r\nIf\r\ne.Cookies.ContainsKey(\"username\")\r\nAndAlso e.Cookies.ContainsKey(\"password\")\r\nThen\r\n\r\n'实际开发的时候,请改为根据用户表验证身份\r\n\r\nDim\r\nusername As\r\nString  = e.Cookies(\"username\")\r\n    Dim password\r\nAs String  =\r\ne.Cookies(\"password\")\r\n    If username\r\n= \"张三\"\r\nAndAlso password\r\n= \"888\" Then\r\n        Verified  =\r\nTrue\r\n    End\r\nIf\r\nEnd\r\nIf\r\nIf\r\nVerified = False\r\nThen\r\n    e.AppendCookie(\"error\",\"用户身份验证失败!\")\r\n'通过Cookie返回错误信息.\r\n    Return\r\n\r\nEnd\r\nIf\r\nSelect\r\nCase e.Path\r\n    Case \"gettime.htm\"\r\n        e.WriteString(Date.now)\r\nEnd\r\nSelect\r\n\r\n2、在客户端项目的命令窗口运行以下代码，可以获取服务器时间：\r\n\r\nDim\r\nhc As\r\nNew HttpClient(\"http://127.0.0.1/gettime.htm\")\r\nhc.sCookies.Add(\"username\",\"张三\")\r\nhc.sCookies.Add(\"password\",\"888\")\r\nDim\r\nval As\r\nString = hc.GetData()\r\nIf\r\nhc.rCookies.ContainsKey(\"error\")\r\nThen\r\n    MessageBox.show(hc.RCookies(\"error\"),\r\n\"错误\",\r\nMessageBoxButtons.OK,\r\nMessageBoxIcon.Warning)\r\nElse\r\n    Dim dt\r\nAs Date =\r\nval\r\n    MessageBox.Show(dt)\r\nEnd\r\nIf\r\n\r\n上述代码中，用户名和密码是通过Cookie提交到服务器的。\r\n如果修改代码中的用户名或密码，执行后会提示“用户身份验证失败”，这个提示是服务器通过Cookie返回的。\r\n\r\n## 模拟表单\r\n\r\n模拟表单\r\n\r\n\r\n**模拟表单**\r\n\r\nHttpClient有个FormData属性，这是一个字典，用于添加需要向服务器提交的表单数据。\r\n\r\n**一个例子**\r\n\r\n1、服务端项目的HttpRequest事件代码:\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"AddNew.htm\"\r\n        Dim\r\nnms() As\r\nString = {\"产品\",\"客户\",\"数量\",\"单价\",\"日期\"}\r\n        For  Each\r\nnm As\r\nString In\r\nnms\r\n            If e.PostValues.ContainsKey(nm)\r\n= False Then\r\n\r\n                e.AppendCookie(\"error\",\"请输入\"\r\n& nm)\r\n                Return\r\n            End\r\nIf\r\n        Next\r\n        Dim\r\ndr As\r\nDataRow = DataTables(\"订单\").AddNew()\r\n        For  Each\r\nnm As\r\nString In\r\nnms\r\n\r\ndr(nm)\r\n= e.PostValues(nm)\r\n        Next\r\n        dr.Save()\r\n        e.WriteString(dr(\"\\_Identify\"))\r\n'返回主键列的值给客户端\r\n        dr.Remove()\r\n'移除此行\r\nEnd\r\nSelect\r\n\r\n2、客户端项目可以通过以下代码增加订单，并获得新增订单的主键：\r\n\r\nDim\r\nhc As\r\nNew HttpClient(\"http://127.0.0.1/AddNew.htm\")\r\nhc.FormData.Add(\"产品\",\"PD01\")\r\nhc.FormData.Add(\"客户\",\"CS01\")\r\nhc.FormData.Add(\"数量\",\"100\")\r\nhc.FormData.Add(\"单价\",\"98\")\r\nhc.FormData.Add(\"日期\",Date.Today)\r\nDim\r\nret As\r\nString = hc.getData()\r\nIf\r\nhc.rCookies.ContainsKey(\"error\")\r\nThen\r\n    MessageBox.Show(hc.rCookies(\"error\"),\r\n\"提示\",\r\nMessageBoxButtons.OK\r\n,MessageBoxIcon.Warning)\r\nElse\r\n    MessageBox.Show(\"增加行成功,新增行的主键为:\"\r\n& ret)\r\nEnd\r\nIf\r\n\r\n## 接收文件\r\n\r\n接收文件\r\n\r\n\r\n**接收文件**\r\n\r\nHttpClient的GetFile方法用于接收从服务器返回的文件，语法为：\r\n\r\nGetFile的语法为：\r\n\r\nGetFile(FielName)\r\n\r\nFileName：将接收到的文件保存在本地的文件名，含路径。\r\n\r\n如果成功接收并保存文件，GetFile会返回True，否则返回False。\r\n\r\n**一个例子**\r\n\r\n1、服务端项目的HttpRequest事件代码：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"download.htm\"\r\n        e.WriteFile(\"d:\\web\\abc.jpg\")\r\nEnd\r\nSelect\r\n\r\n2、客户端项目可以通过以下代码下载文件：\r\n\r\nDim\r\nhc As\r\nNew HttpClient(\"http://127.0.0.1/download.htm\")\r\nIf\r\nhc.GetFile(\"c:\\data\\123.jpg\")\r\nThen\r\n\r\nMessageBox.show(\"文件下载成功\",\"提示\")\r\nElse\r\n    MessageBox.show(\"文件下载失败\",\"提示\")\r\nEnd\r\nIf\r\n\r\n下载的文件会保存为c:\\data\\123.jpg\r\n\r\n## 上传文件\r\n\r\n上传文件\r\n\r\n\r\n**上传文件**\r\n\r\n通过HttpClient上传文件非常简单，只需将要上传的文件添加到HttpClient的Files集合即可。\r\n\r\n向Files添加上传文件的语法为：\r\n\r\nAdd(ID，FileName)\r\n\r\n|  |  |\r\n| --- | --- |\r\n| ID | 文件上传组件的ID。 |\r\n| FileName | 要上传的文件，含路径，一个ID下可以添加多个文件。 |\r\n\r\n关于HttpServer是如何接收和保存客户端上传文件的，请参考：[文件的上传与接收](0030.htm)\r\n\r\n**一个例子**\r\n\r\n1、服务端项目的HttpRequest事件代码：\r\n\r\nDim\r\nVerified As\r\nBoolean\r\nIf\r\ne.PostValues.ContainsKey(\"username\")\r\nAndAlso e.PostValues.ContainsKey(\"password\")\r\nThen\r\n\r\n'实际开发的时候,请改为根据用户表验证身份\r\n\r\nDim\r\nusername As\r\nString  = e.PostValues(\"username\")\r\n    Dim password\r\nAs String\r\n= e.PostValues(\"password\")\r\n    If username\r\n= \"张三\"\r\nAndAlso password\r\n= \"888\" Then\r\n        Verified  =\r\nTrue\r\n    End\r\nIf\r\nEnd\r\nIf\r\nIf\r\nVerified = False\r\nThen\r\n    e.AppendCookie(\"error\",\"用户身份验证失败!\")\r\n'通过Cookie返回错误信息.\r\n\r\nReturn\r\nEnd\r\nIf\r\nSelect\r\nCase e.Path\r\n    Case \"upload.htm\"\r\n        Dim\r\nsb As New\r\nStringBuilder\r\n        sb.AppendLine(\"服务器收到的文件有:\")\r\n        For Each\r\nkey As\r\nString In\r\ne.Files.Keys\r\n            sb.AppendLine(Key)\r\n            For Each\r\nfl As\r\nString In\r\ne.Files(key)\r\n                sb.AppendLine(\"\r\n\" & fl)\r\n                e.SaveFile(key,fl,\"d:\\web\\uploadfiles\\\"\r\n& fl)\r\n'保存接收到的文件\r\n            Next\r\n        Next\r\n        e.WriteString(sb.ToString)\r\nEnd\r\nSelect\r\n\r\n2、客户端可以通过以下代码上传文件：\r\n\r\nDim\r\nhc As\r\nNew HttpClient(\"http://127.0.0.1/upload.htm\")\r\nhc.FormData.Add(\"username\",\"张三\")\r\nhc.FormData.Add(\"password\",\"888\")\r\nhc.Files.Add(\"up1\",\"c:\\data\\abc.jpg\")\r\nhc.Files.Add(\"up1\",\"c:\\data\\订单.xls\")\r\nhc.Files.Add(\"up2\",\"c:\\data\\附件.zip\")\r\nDim\r\nret As\r\nString = hc.getData()\r\nIf\r\nhc.rCookies.ContainsKey(\"error\")\r\nThen\r\n    MessageBox.Show(hc.rCookies(\"error\"))\r\nElse\r\n    MessageBox.Show(ret)\r\nEnd\r\nIf\r\n\r\n提示：本示例的身份信息通过表单传递，而不是通过Cookie，使用哪种方式都可以，记得实际开发的时候，不要使用username和password这样指示性很强的名称。\r\n\r\n## 接收DataTable\r\n\r\n接收DataTable\r\n\r\n\r\n**接收DataTable**\r\n\r\nHttpRequest事件有个WriteDataTable方法，用于向客户端发送DataTable，语法为：\r\n\r\nWriteDataTable(DataTable)\r\n\r\n或\r\n\r\nWriteDataTable(SelectString, ConnectionName)\r\n\r\n|  |  |\r\n| --- | --- |\r\n| DataTable | 要发送的一个现有DataTable |\r\n| SelectString | SelectString语句，据此生成一个DataTable发送给客户端。 |\r\n| ConnectionName | 数据源名称 |\r\n\r\n对应的HttpClient有个GetDataTable方法，用于接收服务端发来的DataTable。\r\n\r\n**一个例子**\r\n\r\n1、服务端项目的HttpRequest事件代码：\r\n\r\nDim\r\nVerified As\r\nBoolean\r\nIf\r\ne.PostValues.ContainsKey(\"username\")\r\nAndAlso e.PostValues.ContainsKey(\"password\")\r\nThen\r\n\r\n'实际开发的时候,请改为根据用户表验证身份\r\n\r\nDim\r\nusername As\r\nString  = e.PostValues(\"username\")\r\n    Dim password\r\nAs String\r\n= e.PostValues(\"password\")\r\n    If username\r\n= \"EP01\" AndAlso\r\npassword = \"888\"\r\nThen\r\n        Verified  =\r\nTrue\r\n    End\r\nIf\r\nEnd\r\nIf\r\nIf\r\nVerified = False\r\nThen\r\n    e.AppendCookie(\"error\",\"用户身份验证失败!\")\r\n'通过Cookie返回错误信息.\r\n    Return\r\nEnd\r\nIf\r\nSelect\r\nCase e.Path\r\n    Case \"query.htm\"\r\n        Dim\r\nscd As\r\nString  = \"select Top\r\n100 \\* from\r\n订单\r\nWhere\r\n雇员\r\n= '\"\r\n& e.PostValues(\"username\")\r\n& \"'\"\r\n        If e.PostValues.ContainsKey(\"filter\")\r\nThen\r\n            scd =\r\nscd &\r\n\" and (\" &\r\ne.PostValues(\"filter\")\r\n& \")\"\r\n        End If\r\n        e.WriteDataTable(scd,\r\n\"数据源名称\")\r\nEnd\r\nSelect\r\n\r\n重要提示：\r\n\r\n在合成表达式的时候，必要条件(只加载登录用户负责的的订单，这里是：雇员='EP01')和查询条件，必须按照以下格式合并：\r\n\r\n必要条件 And (查询条件)\r\n\r\n如果查询条件没有用括号括起来，那么用户可以很轻松地突破权限设置，获取所有数据。\r\n\r\n2、在客户端项目新建一个窗口，插入一个Table和按钮，按钮的Click事件代码设置为：\r\n\r\nDim\r\nhc As\r\nNew HttpClient(\"http://127.0.0.1/query.htm\")\r\nhc.FormData.Add(\"username\",\"EP01\")\r\nhc.FormData.Add(\"password\",\"888\")\r\nhc.FormData.Add(\"filter\",\"产品='PD01'\")\r\nDim\r\ndt As\r\nDataTable = hc.GetDataTable()\r\nIf\r\nhc.rCookies.ContainsKey(\"error\")\r\nThen\r\n    MessageBox.show(hc.rCookies(\"error\"),\r\n\"错误\"\r\n,MessageBoxButtons.OK,\r\nMessageBoxIcon.Warning)\r\nElseIf\r\ndt IsNot\r\nNothing Then\r\n    Tables(\"窗口1\\_Table1\").DataSource\r\n= dt\r\nElse\r\n\r\nMessageBox.Show(\"接收DataTable失败,原因未知!\",\r\n\"错误\" ,MessageBoxButtons.OK\r\n,MessageBoxIcon.Warning)\r\nEnd\r\nIf\r\n\r\n打开窗口，单击按钮，Table控件即可显示从服务端获取的数据。\r\n\r\n## 跳过错误提示\r\n\r\n跳过错误提示\r\n\r\n\r\n跳过错误提示\r\n\r\n将HttpClient的SkipError属性设置为True，可以跳过错误提示，例如下面的代码：\r\n\r\n\r\n\r\nDim\r\nhc As\r\nNew HttpClient(\"https://www.baidu.com/img/abc.jpg\")\r\n\r\nIf\r\nhc.GetFile(\"c:\\data\\123.png\")\r\nThen\r\n\r\n\r\nMessageBox.show(\"文件下载成功\",\"提示\")\r\n\r\nElse\r\n\r\n\r\nMessageBox.show(\"文件下载失败\",\"提示\")\r\n\r\nEnd\r\nIf\r\n\r\n\r\n\r\n由于文件不存在，系统会先出现404的错误提示，然后才会提示下载失败。\r\n\r\n如果希望文件不存在时，跳过错误提示，而是直接提示下载失败，可将SkipError属性设置为True:\r\n\r\nDim\r\nhc As\r\nNew HttpClient(\"https://www.baidu.com/img/abc.jpg\")\r\n\r\nhc.SkipError\r\n= True\r\n\r\nIf\r\nhc.GetFile(\"c:\\data\\123.png\")\r\nThen\r\n\r\n\r\nMessageBox.show(\"文件下载成功\",\"提示\")\r\n\r\nElse\r\n\r\n\r\nMessageBox.show(\"文件下载失败\",\"提示\")\r\n\r\nEnd\r\nIf\r\n\r\n\r\n\r\n其他错误也可以跳过。\r\n\r\n我们可以通过StatusCode属性，进一步细分错误原因，例如：\r\n\r\n\r\n\r\nDim\r\nhc As\r\nNew HttpClient(\"https://www.baidu.com/img/abc.jpg\")\r\n\r\nhc.SkipError\r\n= True\r\n\r\nIf\r\nhc.GetFile(\"c:\\data\\123.png\")\r\nThen\r\n\r\n\r\nMessageBox.show(\"文件下载成功\",\"提示\")\r\n\r\nElse\r\n\r\n\r\nIf hc.StatusCode\r\n= 0\r\nThen\r\n\r\n\r\nMessageBox.show(\"无法连接到服务器!\")\r\n\r\n\r\nElseIf  hc.StatusCode\r\n= 404\r\nThen\r\n\r\n\r\nMessageBox.show(\"要访问的资源不存在!\")\r\n\r\n\r\nElse\r\n\r\n        MessageBox.show(\"文件下载失败，StatuCode:\"\r\n& hc.StatusCode,\"提示!\")\r\n\r\n    End\r\nIf\r\n\r\nEnd\r\nIf\r\n\r\n## 获取内容信息\r\n\r\n获取内容信息\r\n\r\n\r\n**获取内容信息**\r\n\r\n我们可以获取内容信息，例如最近一次修改时间、内容类型、内容长度、状态码、状态字符串和头部信息等。\r\n\r\n例如：\r\n\r\nDim\r\nhtc As\r\nnew HttpClient(\"http://www.foxtable.com/bbs/images/foxtable.gif\")\r\nhtc.GetFile(\"c:\\aaa\\test.gif\")\r\nOutPut.Show(\"更新日期:\"\r\n& htc.LastModified)\r\nOutPut.Show(\"状态码:\"\r\n& htc.StatusCode)\r\nOutPut.Show(\"状态描述:\"\r\n& htc.StatusDescription)\r\nOutPut.Show(\"内容类型:\"\r\n& htc.ResponseContentType)\r\nOutPut.Show(\"内容长度:\"\r\n& htc.ResponseContentLength)\r\nFor\r\nEach key\r\nAs String\r\nIn htc.ResponseHeaders.Keys\r\n'显示服务器返回的头部信息\r\n    OutPut.Show(Key\r\n& \":\"\r\n& htc.ResponseHeaders(key))\r\nNext\r\n\r\n第三方开发接口示例\r\n\r\n下面是进行企业微信开发的时候，一段下载临时素材的代码：\r\n\r\nDim\r\nul As\r\nString =\r\n\r\n\"https://qyapi.weixin.qq.com/cgi-bin/media/get?access\\_token={0}&media\\_id={1}\"\r\nDim\r\nmediaID As\r\nString =\r\n\r\n\"1GDEyyL7RIo868rKmIf3ThZoCMkd69VvH3wPBMpQj9w3uq7dmdxlbNjh669KPLyhF7EEOpBszls7BSmgMFgn07A\"\r\n '要获取素材的ID\r\nDim\r\nhc As\r\nnew HttpClient(CExp(ul,Functions.Execute(\"GetQYAccessToken\"),mediaID))\r\nDim\r\nfl As\r\nString  =\r\n\"c:\\data\\abc.jpg\" '要保存为的本地文件\r\nIf\r\nhc.GetFile(fl)\r\nThen\r\n    **If hc.ResponseContentType.StartsWith(\"application/json\")****Then**\r\n        MessageBox.Show(Filesys.ReadAllText(fl))\r\n        FileSys.DeleteFile(fl)\r\n'删除文件\r\n    Else\r\n        MessageBox.Show(\"图片素材下载成功!\")\r\n    End\r\nIf\r\nElse\r\n    MessageBox.Show(\"图片素材下载失败!\")\r\nEnd\r\nIf\r\n\r\n下载临时素材,会有两种可能：\r\n\r\n1、获取成功，返回文件。\r\n2、获取失败，返回JSON格式的错误信息。\r\n\r\n但是HttpClient的GetFile会直接将返回内容保存为文件，不管是返回的内容是素材文件还是错误信息。\r\n我们可以判断HttpClient的ResponseContentType属性，如果其值为\"application/json\"，说明返回的不是素材文件，而是错误提示信息，然后从此文件读取错误信息显示给用户，最后删除文件。\r\n\r\n接下来你会看到，我们还有更好的办法应付此类场景。\r\n\r\n## 分两步获取信息和内容\r\n\r\n分两步获取信息和内容\r\n\r\n\r\n**分两步获取信息和内容**\r\n\r\nHttpClient有个GetInformation方法，用于获取内容信息，获取成功则返回True，否则返回False。\r\n\r\n此方法执行之后并不会关闭和服务端的连接，我们可以继续用GetFile、GetData等方法获取内容本身。\r\n\r\n有了这个方法，我们可以首先获取内容信息，然后根据内容信息进行下一步的操作，而无需重新连接服务器。\r\n\r\n**示例一**\r\n\r\n服务端的HttpRequest事件代码为：\r\n\r\nSelect\r\nCase e.Path\r\n    Case\r\n\"test.jpg\"\r\n        If\r\nDate.Now.Hour\r\n< 10 Then\r\n            e.ResponseHeaders(\"Error\")\r\n= \"001\"\r\n            e.WriteString(\"文件还未准备好,请10点之后再下载!\")\r\n        Else\r\n            e.WriteFile(\"d:\\web\\test.jpg\")\r\n        End If\r\nEnd\r\nSelect\r\n\r\n如果客户在上午10点之前访问，则在头部信息中加上错误标记，并返回文本形式的错误信息。\r\n\r\n客户端代码：\r\n\r\nDim\r\nhtc As\r\nnew HttpClient(\"http://127.0.0.1/test.jpg\")\r\nIf\r\nhtc.GetInformation()\r\nThen\r\n    Dim err\r\nAs String  =\r\nhtc.ResponseHeaders(\"Error\")\r\n    If  err>\r\n\"\" Then\r\n        MessageBox.Show(htc.GetData()\r\n,\"错误\",\r\nMessageBoxButtons.OK,\r\nMessageBoxIcon.Error)\r\n    Else\r\n        htc.GetFile(\"c:\\data\\test.jpg\")\r\n        MessageBox.Show(\"文件下载成功\",\r\n\"提示\",\r\nMessageBoxButtons.OK,\r\nMessageBoxIcon.Information)\r\n    End\r\nIf\r\nElse\r\n        MessageBox.Show(\"文件下载失败\",\r\n\"提示\",\r\nMessageBoxButtons.OK,MessageBoxIcon.Error)\r\nEnd\r\nIf\r\n\r\n**示例二**\r\n\r\n服务端可以只发送内容信息，而不发送内容，例如服务端的HttpRequest事件代码为：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"test.jpg\"\r\n        If\r\nDate.Now.Hour\r\n< 10 Then\r\n            e.ResponseHeaders(\"Error\")\r\n= \"The file is not ready\"\r\n            e.Handled\r\n= True\r\n        Else\r\n            e.WriteFile(\"d:\\web\\test.jpg\")\r\n        End\r\nIf\r\nEnd Select\r\n\r\n如果客户在上午10点之前访问，则通过头部信息返回错误\"The\r\nfile is not ready!\"\r\n\r\n**重要提示：**\r\n\r\n1、如果不打算发送内容本身，只发送内容信息，那么务必将e参数Handled设置为True，否则客户端将持续等待，直到超时。\r\n2、头部信息不支持中文，解决方法后面会介绍。\r\n\r\n客户端代码：\r\n\r\nDim\r\nhtc As\r\nnew HttpClient(\"http://127.0.0.1/test.jpg\")\r\nIf\r\nhtc.GetInformation()\r\nThen\r\n    Dim err\r\nAs String  =\r\nhtc.ResponseHeaders(\"Error\")\r\n    If  err>\r\n\"\" Then\r\n        MessageBox.Show(err,\"错误\",\r\nMessageBoxButtons.OK,\r\nMessageBoxIcon.Error)\r\n    Else\r\n        htc.GetFile(\"c:\\data\\test.jpg\")\r\n        MessageBox.Show(\"文件下载成功\",\r\n\"提示\",\r\nMessageBoxButtons.OK,\r\nMessageBoxIcon.Information)\r\n    End\r\nIf\r\nElse\r\n        MessageBox.Show(\"文件下载失败\",\r\n\"提示\",\r\nMessageBoxButtons.OK,MessageBoxIcon.Error)\r\nEnd\r\nIf\r\n\r\n**第三方开发接口示例**\r\n\r\n有了GetInformation方法，我们进行第三方接口开发的时候，会更加方便，还是以上一节企业微信下载临时素材为例，改进后的代码为：\r\n\r\nDim\r\nul As\r\nString =\r\n\r\n\"https://qyapi.weixin.qq.com/cgi-bin/media/get?access\\_token={0}&media\\_id={1}\"\r\nDim\r\nmediaID As\r\nString =\r\n\"1GDEyyL7RIo868rKmIf3ThZoCMkd69VvH3wPBMpQj9w3uq7dmdxlbNjh669KPLyhF7EEOpBszls7BSmgMFgn07A\"\r\n'要获取素材的ID\r\nDim\r\nhc As\r\nnew HttpClient(CExp(ul,Functions.Execute(\"GetQYAccessToken\"),mediaID))\r\nDim\r\nfl As\r\nString  =\r\n\"c:\\data\\abc.jpg\" '要保存为的本地文件\r\nIf\r\nhc.GetInformation()\r\nThen\r\n    If hc.ResponseContentType.StartsWith(\"application/json\")\r\nThen\r\n        MessageBox.Show(hc.GetData())\r\n    Else\r\n        hc.GetFile(fl)\r\n\r\nMessageBox.Show(\"图片素材下载成功!\")\r\n    End\r\nIf\r\nElse\r\n    MessageBox.Show(\"图片素材下载失败!\")\r\nEnd\r\nIf\r\n\r\n## 单纯获取内容信息\r\n\r\n单纯获取内容信息\r\n\r\n\r\n**单纯获取内容信息**\r\n\r\n如果只需获取内容信息，而不需要获取内容本身，可以参考下面的代码。\r\n\r\n例如：\r\n\r\nDim\r\nhtc\r\nAs\r\nnew\r\nHttpClient(\"http://www.foxtable.com/bbs/images/foxtable.gif\")\r\n**htc****.GetInformation()**\r\n**htc.Close() '必须执行Close方法关闭连接**\r\nOutPut.Show(\"更新日期:\"\r\n&\r\nhtc.LastModified)\r\nOutPut.Show(\"状态码:\"\r\n&\r\nhtc.StatusCode)\r\nOutPut.Show(\"状态描述:\"\r\n&\r\nhtc.StatusDescription)\r\nOutPut.Show(\"内容类型:\"\r\n&\r\nhtc.ResponseContentType)\r\nOutPut.Show(\"内容长度:\"\r\n&\r\nhtc.ResponseContentLength)\r\nFor\r\nEach\r\nkey\r\nAs\r\nString\r\nIn\r\nhtc.ResponseHeaders.Keys\r\n'显示服务器返回的头部信息\r\n    OutPut.Show(Key\r\n&\r\n\":\"\r\n&\r\nhtc.ResponseHeaders(key))\r\nNext\r\n\r\n重要提示：\r\n\r\n如果不获取内容本身，那么一定要执行Close方法关闭HttpClient，否则和服务器之间的链接将一直存在，直到超时，导致服务器资源得不到及时释放。\r\n\r\n## 再谈头部信息\r\n\r\n再谈头部信息\r\n\r\n\r\n**再谈头部信息**\r\n\r\n我们已经知道，Http服务器和客户端之间可以通过Post、Get、Cookies传递信息，其中Post和Get都是单向的，Cookies是双向的。\r\n\r\n前面两节，我们用了一个新的数据传递方法：通过头部(Headers)传递信息。\r\n\r\nHeaders一样可以双向传递，不同的是Cookies可以存储在客户端供下次访问使用，而Headers只能是临时的。\r\n\r\n此外Headers不支持中文，如果需要用中文，可用UrlEncode和UrlDecode函数进行编码和解码。\r\n\r\n你可能已经知道，服务端和客户端之间默认已经有不少头部信息，例如传递客户端的浏览器类型、服务器类型、HTTP协议版本等等。\r\n\r\n**服务端的HttpRequest事件有两个和头部信息相关的属性：**\r\n\r\n* Headers\r\n  用于获取客户端发来的头部信息。\r\n* ResponseHeaders\r\n  用于设置向客户端发送的头部信息。\r\n\r\n**客户端类HttpClient有两个和头部信息相关的属性：**\r\n\r\n* Headers\r\n  用于设置向服务端发送的头部信息。\r\n* ResponseHeaders\r\n  用于获取服务端发来的头部信息。\r\n\r\n在针对一些第三方API接口进行开发时，可能需要使用头部信息，参考：[阿里云全国物流查询接口](0246.htm)。\r\n\r\n**示例**\r\n\r\n服务端的HttpRrquest事件代码\r\n\r\nSelect\r\nCase e.Path\r\n\r\nCase \"test.jpg\"\r\n\r\n'获取客户端发送来的头部信息\r\n\r\nDim\r\nauthor As\r\nString  =\r\nUrlDecode(e.Headers(\"author\"))\r\n        Dim age\r\nAs String  =\r\ne.Headers(\"age\")\r\n\r\n'向客户端发送头部信息\r\n        e.ResponseHeaders.Add(\"brief\",URLEncode(\"你不是因为厉害才开始,你是因为开始才厉害\"))\r\n        e.ResponseHeaders.Add(\"pages\",600)\r\n        '发送实际内容\r\n        e.WriteFile(\"d:\\web\\test.jpg\")\r\nEnd\r\nSelect\r\n\r\n客户端代码：\r\n\r\nDim\r\nhtc As\r\nnew HttpClient(\"http://127.0.0.1/test.jpg\")\r\nhtc.Headers.Add(\"author\",\r\nUrlEncode(\"舒淇\"))\r\n'向服务端附加头部信息\r\nhtc.Headers.Add(\"age\",\r\n30)\r\nIf\r\nhtc.GetInformation()\r\nThen\r\n\r\n'获取服务端返回的头部信息\r\n\r\nDim\r\nbrief As\r\nString   =\r\nUrlDecode(htc.ResponseHeaders(\"brief\"))\r\n    Dim pages\r\nAs Integer =\r\nhtc.ResponseHeaders(\"pages\")\r\n    '获取实际内容\r\n    htc.GetFile(\"c:\\data\\test.jpg\")\r\n    MessageBox.Show(\"文件下载成功\",\r\n\"提示\",\r\nMessageBoxButtons.OK,\r\nMessageBoxIcon.Information)\r\nElse\r\n    MessageBox.Show(\"文件下载失败\",\r\n\"提示\",\r\nMessageBoxButtons.OK,MessageBoxIcon.Error)\r\nEnd\r\nIf\r\n\r\n## 一个经典示例\r\n\r\n一个经典示例\r\n\r\n\r\n**一个经典示例**\r\n\r\n实现网络环境下实现复杂编号，一直是个不轻松的任务，Foxtable 2017之前的实现方法，你可以参考：\r\n\r\n[网络环境下的复杂编号](http://www.foxtable.com/webhelp/scr/1994.htm)\r\n\r\n[用OpenQQ实现网络环境下的编号](http://www.foxtable.com/webhelp/scr/3008.htm)\r\n\r\n可以看到，即使有了OpenQQ，完成这个任务还是比较麻烦。\r\n\r\n我们现在改用HttpServer+HttpClient来实现，你会发现代码简单了很多。\r\n\r\n我们用其中最复杂的一个例子为例讲述。\r\n\r\n\r\n\r\n假定有个名为\"工程\"的表，需要按月自动生成编号，根据工程代码按顺序编号，前4位是工程代码，然后是4位年，2位月，最后4位是顺序号，如下图所示：\r\n\r\n重要提示：客户端和服务端必须分开两个项目运行，否则会锁死。\r\n\r\n设计步骤：\r\n\r\n1、服务端项目的HttpRequest事件代码：\r\n\r\nStatic\r\nbhs As\r\nnew Dictionary(of\r\nString,Integer)\r\n'这个字典用于记录每个前缀的下一序号\r\nIf\r\ne.Path =\r\n\"bianhao.htm\"\r\n    If e.PostValues.ContainsKey(\"制单日期\")\r\nAndAlso e.PostValues.ContainsKey(\"工程代码\")\r\nThen\r\n        Dim max\r\nAs Integer\r\n        Dim bh\r\nAs String\r\n        Dim\r\nprefix As\r\nString = e.PostValues(\"工程代码\")\r\n& \"-\"\r\n&  Format(CDate(e.PostValues(\"制单日期\")),\r\n\"yyyyMM\")\r\n        If bhs.ContainsKey(prefix)\r\n= False Then\r\n'如果字典中不存在次前缀的序号,\r\n            Dim\r\ncmd As New\r\nSQLCommand\r\n            'cmd.ConnectionName = \"数据源名称\"\r\n'外部表请设置数据源名称'\r\n            cmd.CommandText\r\n= \"Select max(单据编号) From {工程} Where 单据编号 Like '\"\r\n& prefix\r\n& \"%'\"\r\n\r\nbh\r\n= cmd.ExecuteScalar\r\n            If\r\nbh IsNot Nothing\r\nThen\r\n                bh =\r\nbh.SubString(12)\r\n                Integer.TryParse(bh,max)\r\n            End If\r\n            bhs.Add(prefix,max)\r\n        Else\r\n            max =\r\nbhs(prefix)\r\n        End If\r\n        max =\r\nmax + 1\r\n        bhs(prefix)\r\n= max\r\n        e.WriteString(Prefix\r\n& \"-\"\r\n& Format(max,\"0000\"))\r\n    End\r\nIf\r\nEnd\r\nIf\r\n\r\n2、在客户端项目工程表的DataColChanged事件加上代码：\r\n\r\nSelect\r\nCase e.DataCol.name\r\n    Case\r\n\"工程代码\",\"制单日期\"\r\n        Dim dr\r\nAs DataRow =\r\ne.DataRow\r\n        If dr.IsNull(\"工程代码\")\r\n= False AndAlso\r\ndr.IsNull(\"制单日期\")\r\n= False Then\r\n\r\nDim\r\nhc As\r\nnew HttpClient(\"http://127.0.0.1/bianhao.htm\")\r\n            hc.FormData.Add(\"工程代码\",dr(\"工程代码\"))\r\n            hc.FormData.Add(\"制单日期\",dr(\"制单日期\"))\r\n            dr(\"单据编号\")\r\n= hc.GetData()\r\n\r\nEnd\r\nIf\r\nEnd\r\nSelect\n\nFile v2.0.5:references/08_JSON解析.md\n\n# JSON解析\r\n\r\n\r\n## 解析JSON\r\n\r\n解析JSON\r\n\r\n\r\n**解析JSON**\r\n\r\nJSON的解析，Foxtable采用的是第三方库Newtonsoft.JSON。\r\n\r\nFoxtable已经引入了以下两个命名空间：\r\n\r\nNewtonsoft.Json\r\nNewtonsoft.Json.Linq\r\n\r\n你可以直接在代码中使用Newtonsoft.JSON进行JSON的系列化和反系列化。\r\n\r\n如果已经熟悉了Newtonsoft.JSON，可以跳过本节内容。\r\n\r\n示例一\r\n\r\n简单JSON对象的解析和注意事项。\r\n\r\n在命令窗口执行：\r\n\r\nDim\r\njson As\r\nString =\r\n\"{'name':'李云龙','age':'36'}\"\r\nDim\r\njo As\r\nJObject = JObject.Parse(json)\r\nOutput.Show(jo(\"name\"))\r\nOutput.Show(jo(\"age\"))\r\n\r\n显示的结果是：\r\n\r\n李云龙\r\n36\r\n\r\nJSON对象成员可以直接转换为相应的数据类型，例如：\r\n\r\nDim\r\njson As\r\nString =\r\n\"{'name':'李云龙','age':'36'}\"\r\nDim\r\njo As\r\nJObject = JObject.Parse(json)\r\nDim\r\nName As\r\nString  = jo(\"name\")\r\nDim\r\nAge As\r\nInteger = jo(\"age\")\r\n\r\n但是有一个很奇怪的现象，直接将JSON成员和字符串组合会出错，例如以下代码是无法执行的的：\r\n\r\nDim\r\njson As\r\nString =\r\n\"{'name':'李云龙','age':'36'}\"\r\nDim\r\njo As\r\nJObject = JObject.Parse(json)\r\nOutput.Show(\"姓名:\"\r\n& jo(\"name\")\r\n& \" 年龄:\"\r\n& jo(\"age\"))\r\n\r\n必须改为:\r\n\r\nDim\r\njson As\r\nString =\r\n\"{'name':'李云龙','age':'36'}\"\r\nDim\r\njo As\r\nJObject = JObject.Parse(json)\r\nOutput.Show(\"姓名:\"\r\n& jo(\"name\").ToString()\r\n& \" 年龄:\"\r\n& jo(\"age\").ToString())\r\n\r\n此外JSON对象是区分大小写的，所以上述代码中，如果你改用JO(\"Name\")是取不到值的，只能是jo(\"name\")。\r\n\r\n实际上你用JO(\"Name\")取值，并不会出错，而是返回一个空值(Nothing)，我们可以用这个特性判断JSON对象是否包括指定名称的成员，例如：\r\n\r\nDim\r\njson As\r\nString =\r\n\"{'name':'李云龙','age':'36'}\"\r\nDim\r\njo As\r\nJObject = JObject.Parse(json)\r\nOutput.show(\r\njo(\"Name\")\r\nIs Nothing )\r\nOutput.show(\r\njo(\"Test\")\r\nIs Nothing )\r\n\r\n显示的结果是：\r\n\r\nTrue\r\nTrue\r\n\r\n**示例二**\r\n\r\n解析嵌套对象。\r\n\r\n在命令窗口执行：\r\n\r\nDim\r\njson As\r\nString =\r\n\r\n\"{'name':'李云龙','age':'36','card':{'bank':'工行','account':'123456'}}\"\r\nDim\r\njo As\r\nJObject = JObject.Parse(json)\r\nOutput.Show(jo(\"name\"))\r\nOutput.Show(jo(\"age\"))\r\nOutput.Show(jo(\"card\")(\"bank\"))\r\nOutput.Show(jo(\"card\")(\"account\"))\r\n\r\n上面的JSON对象嵌套了一个子对象card，这个子对象有自己的属性(bank和account)。\r\n\r\n显示的结果为：\r\n\r\n李云龙\r\n36\r\n工行\r\n123456\r\n\r\n**示例三**\r\n\r\nJSON值数组的解析。\r\n\r\n在命令窗口执行：\r\n\r\nDim\r\njson As\r\nString =\r\n\"['张三','李四','王五']\"\r\nDim\r\nja As\r\nJArray = JArray.Parse(json)\r\nFor\r\ni As\r\nInteger = 0\r\nTo ja.Count\r\n- 1\r\n    output.show(ja(i))\r\nNext\r\n\r\n显示的结果是：\r\n\r\n张三\r\n李四\r\n王五\r\n\r\nJSON数组\r\n的对象为JArray，数组成员的类型为JToken，所以也可以用下面的代码遍历JSON数组:\r\n\r\nDim\r\njson As\r\nString =\r\n\"['张三','李四','王五']\"\r\nFor\r\nEach v\r\nAs JToken\r\nIn JArray.Parse(json)\r\n    Output.show(v)\r\nNext\r\n\r\n**示例四**JSON对象中的值数组的解析。\r\n\r\n在命令窗口执行：\r\n\r\nDim\r\njson As\r\nString  =\r\n\"{'touser':['zhansan',\r\n'lisi'],'msgtype':'text'}\"\r\nDim\r\njo As\r\nJObject = JObject.Parse(json)\r\nDim\r\nja As\r\nJArray = jo(\"touser\")\r\nFor\r\ni As\r\nInteger = 0\r\nTo ja.Count\r\n- 1\r\n   Output.Show(ja(i).ToString)\r\nNext\r\nOutput.Show(jo(\"msgtype\").Tostring)\r\n\r\n上面的JSON对象中，touser属性是一个值数组，这个数组包括两个成员(zhansan和lisi)。\r\n\r\n执行后显示的结果为：\r\n\r\nzhansan\r\nlisi\r\ntext\r\n\r\n同样也可以通过JToken来遍历，得到的结果是一样的：\r\n\r\nDim\r\njson As\r\nString  =\r\n\"{'touser':['zhansan',\r\n'lisi'],'msgtype':'text'}\"\r\nDim\r\njo As\r\nJObject = JObject.Parse(json)\r\nFor\r\nEach jt\r\nAs JToken\r\nIn jo(\"touser\")\r\n\r\nOutput.Show(jt)\r\nNext\r\nOutput.Show(jo(\"msgtype\").Tostring)\r\n\r\n**示例五**\r\n\r\nJSON对象数组的解析。\r\n\r\n在命令窗口执行：\r\n\r\nDim\r\njson As\r\nString =\r\n\"[{'name':'李云龙','age':'36'},{'name':'黄晓明','age':'28'}]\"\r\nDim\r\nja As\r\nJArray = Jarray.Parse(json)\r\nFor\r\ni As\r\nInteger = 0\r\nTo ja.Count\r\n- 1\r\n\r\nOutPut.Show(\"姓名:\"\r\n& ja(i)(\"name\").ToString()\r\n&  \"  年龄:\"\r\n& ja(i)(\"age\").ToString())\r\nNext\r\n\r\n或者：\r\n\r\nDim\r\njson As\r\nString =\r\n\"[{'name':'李云龙','age':'36'},{'name':'黄晓明','age':'28'}]\"\r\nFor\r\nEach ep\r\nAs JToken\r\nIn JArray.Parse(json)\r\n\r\nOutput.Show(\"姓名:\"\r\n& ep(\"name\").ToString()\r\n&  \"  年龄:\"\r\n& ep(\"age\").ToString())\r\nNext\r\n\r\n显示的结果为：\r\n\r\n姓名:李云龙 年龄:36\r\n姓名:黄晓明 年龄:28\r\n\r\n**示例六**\r\n\r\n嵌套的对象数组解析。\r\n\r\n在命令窗口执行：\r\n\r\nDim\r\njson As\r\nString =\r\n\r\n\"{'name':'李云龙','card':[{'bank':'工行','account':'123'},{'bank':'建行','account':'678'}]}\"\r\nDim\r\njo As\r\nJObject = JObject.Parse(json)\r\nOutput.Show(jo(\"name\"))\r\nFor\r\nEach jt\r\nAs JToken\r\nIn jo(\"card\")\r\n    Output.Show(jt(\"bank\").ToString\r\n& \"|\"\r\n& jt(\"account\").ToString)\r\nNext\r\n\r\n上面的JSON对象，其card属性是一个对象数组，包括两个银行卡信息。\r\n\r\n显示的结果为：\r\n\r\n李云龙\r\n工行|123\r\n建行|678\r\n\r\n## 解析实例\r\n\r\n解析实例\r\n\r\n\r\n**JSON解析实例**\r\n\r\n下面是[阿里云全国快递查询](0246.htm)接口返回的数据格式，顶层对象包括的属性非常多，其属性showapi\\_res\\_body是一个嵌套对象，这个嵌套对象本身也有很多属性，\r\n其中的data属性是一个对象数组，这个数组的每个成员包括time和context属性：\r\n\r\n{\r\n  \"showapi\\_res\\_code\": 0,\r\n  \"showapi\\_res\\_error\": \"\",\r\n  \"showapi\\_res\\_body\": {\r\n    \"mailNo\": \"929601675231\",\r\n    \"update\": 1488784549365,\r\n    \"updateStr\": \"2017-03-06 15:15:49\",\r\n    \"ret\\_code\": 0,\r\n    \"flag\": true,\r\n    \"status\": 4,\r\n    \"tel\": \"95338\",\r\n    \"expSpellName\": \"shunfeng\",\r\n    \"data\": [\r\n      {\r\n        \"time\": \"2017-03-03 08:42:59\",\r\n        \"context\": \"已签收,感谢使用顺丰,期待再次为您服务\"\r\n      },\r\n      {\r\n        \"time\": \"2017-03-03 07:40:22\",\r\n        \"context\": \"正在派送途中,请您准备签收(派件人:李正国,电话:18907153726)\"\r\n      },\r\n      {\r\n        \"time\": \"2017-03-03 06:08:45\",\r\n        \"context\": \"快件到达\r\n【武汉�~口区复兴村营业点】\"\r\n      },\r\n      {\r\n        \"time\": \"2017-03-03 04:48:26\",\r\n        \"context\": \"快件在【武汉吴家山集散中心】已装车，准备发往\r\n【武汉�~口区复兴村营业点】\"\r\n      },\r\n      {\r\n        \"time\": \"2017-03-03 00:07:27\",\r\n        \"context\": \"快件到达\r\n【武汉吴家山集散中心】\"\r\n      },\r\n      {\r\n        \"time\": \"2017-03-02 22:54:14\",\r\n        \"context\": \"快件在【武汉总集散中心】已装车，准备发往\r\n【武汉吴家山集散中心】\"\r\n      },\r\n      {\r\n        \"time\": \"2017-03-02 22:54:02\",\r\n        \"context\": \"快件到达\r\n【武汉总集散中心】\"\r\n      },\r\n      {\r\n        \"time\": \"2017-03-02 15:00:33\",\r\n        \"context\": \"快件在【深圳总集散中心】已装车，准备发往\r\n【武汉总集散中心】\"\r\n      },\r\n      {\r\n        \"time\": \"2017-03-02 14:47:39\",\r\n        \"context\": \"快件到达\r\n【深圳总集散中心】\"\r\n      },\r\n      {\r\n        \"time\": \"2017-03-02 10:24:37\",\r\n        \"context\": \"快件在【江门江海集散中心】已装车，准备发往下一站\"\r\n      },\r\n      {\r\n        \"time\": \"2017-03-02 05:41:54\",\r\n        \"context\": \"快件到达\r\n【江门江海集散中心】\"\r\n      },\r\n      {\r\n        \"time\": \"2017-03-01 22:31:24\",\r\n        \"context\": \"快件在【湛江麻章集散中心】已装车，准备发往\r\n【江门江海集散中心】\"\r\n      },\r\n      {\r\n        \"time\": \"2017-03-01 22:24:37\",\r\n        \"context\": \"快件到达\r\n【湛江麻章集散中心】\"\r\n      },\r\n      {\r\n        \"time\": \"2017-03-01 20:02:01\",\r\n        \"context\": \"快件在【湛江市赤坎文保北村营业点】已装车，准备发往\r\n【湛江麻章集散中心】\"\r\n      },\r\n      {\r\n        \"time\": \"2017-03-01 19:49:28\",\r\n        \"context\": \"顺丰速运\r\n已收取快件\"\r\n      }\r\n    ],\r\n    \"expTextName\": \"顺丰速运\"\r\n  }\r\n}\r\n\r\n首先将上面的JSON数据复制到剪贴版，然后在命令窗口执行下面的代码：\r\n\r\nDim\r\njo As\r\nJObject = Jobject.Parse(ClipBoard.GetText)\r\nIf\r\njo(\"showapi\\_res\\_body\")(\"data\")\r\nIsNot Nothing\r\nThen\r\n    For Each\r\njt As\r\nJToken In\r\njo(\"showapi\\_res\\_body\")(\"data\")\r\n        Output.Show(jt(\"time\").ToString\r\n& \" | \"\r\n& jt(\"context\").ToString)\r\n    Next\r\nEnd\r\nIf\r\n\r\n可以得到解析结果：\r\n\r\n2017-03-03\r\n08:42:59 | 已签收,感谢使用顺丰,期待再次为您服务\r\n2017-03-03 07:40:22 | 正在派送途中,请您准备签收(派件人:李正国,电话:18907153726)\r\n2017-03-03 06:08:45 | 快件到达 【武汉�~口区复兴村营业点】\r\n2017-03-03 04:48:26 | 快件在【武汉吴家山集散中心】已装车，准备发往 【武汉�~口区复兴村营业点】\r\n2017-03-03 00:07:27 | 快件到达 【武汉吴家山集散中心】\r\n2017-03-02 22:54:14 | 快件在【武汉总集散中心】已装车，准备发往 【武汉吴家山集散中心】\r\n2017-03-02 22:54:02 | 快件到达 【武汉总集散中心】\r\n2017-03-02 15:00:33 | 快件在【深圳总集散中心】已装车，准备发往 【武汉总集散中心】\r\n2017-03-02 14:47:39 | 快件到达 【深圳总集散中心】\r\n2017-03-02 10:24:37 | 快件在【江门江海集散中心】已装车，准备发往下一站\r\n2017-03-02 05:41:54 | 快件到达 【江门江海集散中心】\r\n2017-03-01 22:31:24 | 快件在【湛江麻章集散中心】已装车，准备发往 【江门江海集散中心】\r\n2017-03-01 22:24:37 | 快件到达 【湛江麻章集散中心】\r\n2017-03-01 20:02:01 | 快件在【湛江市赤坎文保北村营业点】已装车，准备发往 【湛江麻章集散中心】\r\n2017-03-01 19:49:28 | 顺丰速运 已收取快件\r\n\r\n可以看到，用JObject解析JSON数据是异常方便的。\r\n\r\n## 生成JSON\r\n\r\n生成JSON\r\n\r\n\r\n**生成JSON**\r\n\r\n我们当然可以手工编码生成JSON，但是比较繁琐，容易出错。\r\n\r\n如果采用Newtonsoft.JSON生成，会变得简单可靠。\r\n\r\nFoxtable已经引入了以下两个命名空间：\r\n\r\nNewtonsoft.Json\r\nNewtonsoft.Json.Linq\r\n\r\n你可以直接在代码中使用Newtonsoft.JSON进行JSON的系列化和反系列化。\r\n\r\n如果已经熟悉了Newtonsoft.JSON，可以跳过本节内容。\r\n\r\n示例一\r\n\r\n根据现有对象生成JSON。\r\n\r\n例如根据当前登录用户生成JSON，可在命令窗口执行：\r\n\r\nDim\r\njo As\r\nJObject = JObject.FromObject(User)\r\nDim\r\njs As\r\nString = jo.ToString()\r\nOutput.Show(js)\r\n\r\n显示的结果为：\r\n\r\n{\r\n\"Name\": \"张三\",\r\n\"Group\": null,\r\n\"Type\": 2,\r\n\"Tag\": null,\r\n\"Roles\": \"经理\",\r\n\"Default\": false,\r\n\"ExtendedValues\": {}\r\n}\r\n\r\n示例二\r\n\r\n动态生成JSON。\r\n\r\n在命令窗口执行：\r\n\r\nDim\r\njo As\r\nNew\r\nJObject\r\njo(\"Name\")\r\n= \"张三\"\r\njo(\"Group\")\r\n= \"VIP\"\r\njo(\"Type\")\r\n= 2\r\njo(\"Default\")\r\n= False\r\nOutput.Show(jo.ToString)\r\n\r\n显示的结果为：\r\n\r\n{\r\n\"Name\": \"张三\",\r\n\"Group\": \"VIP\",\r\n\"Type\": 2,\r\n\"Default\": false\r\n}\r\n\r\n**示例三**\r\n\r\n生成包括嵌套对象的JSON。\r\n\r\n在命令窗口执行：\r\n\r\nDim\r\njo As\r\nNew\r\nJObject\r\njo(\"name\")\r\n= \"李云龙\"\r\njo(\"age\")\r\n= \"36\"\r\njo(\"card\")\r\n= New\r\nJObject\r\njo(\"card\")(\"bank\")\r\n= \"工行\"\r\njo(\"card\")(\"account\")\r\n= \"12345678\"\r\nOutput.Show(jo.ToString)\r\n\r\n显示的结果为：\r\n\r\n{\r\n  \"name\": \"李云龙\",\r\n  \"age\": \"36\",\r\n  \"card\": {\r\n    \"bank\": \"工行\",\r\n    \"account\": \"12345678\"\r\n  }\r\n}\r\n\r\n**示例四**\r\n\r\n生成包括数组的JSON。\r\n\r\n在命令窗口执行：\r\n\r\nDim\r\njo As\r\nNew\r\nJObject\r\njo(\"Name\")\r\n= \"张三\"\r\njo(\"Group\")\r\n= \"VIP\"\r\nDim\r\nja As\r\nNew Jarray\r\n'定义数组\r\nja.Add(\"manager\")\r\nja.Add(\"developer\")\r\njo(\"Roles\")\r\n= ja\r\n'将Roles设置为前面定义的数组\r\nOutput.Show(jo.ToString)\r\n\r\n显示的结果为：\r\n\r\n\r\n{\r\n  \"Name\": \"张三\",\r\n  \"Group\": \"VIP\",\r\n  \"Roles\": [\r\n    \"manager\",\r\n    \"developer\"\r\n  ]\r\n}\r\n\r\n下面是一段完全等效的代码，希望大家体会，在实际开发过程中灵活运用：\r\n\r\nDim\r\njo As\r\nNew\r\nJObject\r\nDim\r\nvalues() As\r\nString = {\"manager\",\"developer\"}\r\njo(\"Name\")\r\n= \"张三\"\r\njo(\"Group\")\r\n= \"VIP\"\r\njo(\"Roles\")\r\n= New Jarray(values)\r\nOutput.Show(jo.ToString)\r\n\r\n**示例五**生成包括对象数组的JSON。\r\n\r\n在命令窗口执行：\r\n\r\nDim\r\njo As\r\nNew\r\nJObject\r\nDim\r\nja As\r\nNew\r\nJArray\r\njo(\"dept\")\r\n= \"销售部\"\r\njo(\"mpr\")\r\n= \"赵刚\"\r\njo(\"staff\")\r\n=  ja\r\n'\r\nja.Add(New\r\nJObject)\r\n'给数组添加两个对象成员\r\nja.Add(New\r\nJObject)\r\n\r\nja(0)(\"name\")\r\n= \"李云龙\"\r\nja(0)(\"age\")\r\n= 36\r\nja(1)(\"name\")\r\n= \"黄晓明\"\r\nja(1)(\"age\")\r\n= 26\r\nOutput.Show(jo.ToString)\r\n\r\n显示的结果为：\r\n\r\n{\r\n  \"dept\": \"销售部\",\r\n  \"mpr\": \"赵刚\",\r\n  \"staff\": [\r\n    {\r\n      \"name\": \"李云龙\",\r\n      \"age\": 36\r\n    },\r\n    {\r\n      \"name\": \"黄晓明\",\r\n      \"age\": 26\r\n    }\r\n  ]\r\n}\r\n\r\n对象数组也是一个数组，只不过这个数组的成员也是JObject，JObject的成员可以是数组，数组的成员可以是JObject，二者可以层层嵌套，生成任意复杂的JSON。\r\n\r\n## 生成实例\r\n\r\n生成实例\r\n\r\n\r\n**JSON生成实例**\r\n\r\n在后面学习微信接口的时候，你会发现很多数据都需要用JSON格式提交到微信服务器。\r\n\r\n例如下面是一个JSON格式的微信菜单数据，我们将这段数据提交到微信相关接口后，可以生成一个微信菜单。\r\n这个菜单包括两个顶层按钮，第一个顶层按钮\"今日歌曲\"是一个普通按钮，第二个顶层按钮\"功能\"是一个菜单，这个菜单包括三个子菜单按钮，分别是\"搜索\"、\"视频\"和\"赞一下我们\"：\r\n\r\n{\r\n  \"button\": [\r\n    {\r\n      \"type\": \"click\",\r\n      \"name\": \"今日歌曲\",\r\n      \"key\": \"V1001\\_TODAY\\_MUSIC\"\r\n    },\r\n    {\r\n      \"name\": \"功能\",\r\n      \"sub\\_button\": [\r\n        {\r\n          \"type\": \"view\",\r\n          \"name\": \"搜索\",\r\n          \"url\": \"http://www.soso.com/\"\r\n        },\r\n        {\r\n          \"type\": \"view\",\r\n          \"name\": \"视频\",\r\n          \"url\": \"http://v.qq.com/\"\r\n        },\r\n        {\r\n          \"type\": \"click\",\r\n          \"name\": \"赞一下我们\",\r\n          \"url\": \"V1001\\_GOOD\"\r\n        }\r\n      ]\r\n    }\r\n  ]\r\n}\r\n\r\n我们可以用代码生成以上格式的数据，请务必仔细体会：\r\n\r\nDim\r\nmnu As\r\nNew JObject\r\n'菜单对象\r\nDim\r\nbutton As\r\nNew\r\nJArray\r\nmnu(\"button\")\r\n= button\r\n'菜单对象只有一个属性button,这个是一个数组,包括所有顶层菜单按钮.\r\n'增加第一个顶层按钮.\r\nbutton.Add(New\r\nJobject)\r\nbutton(0)(\"type\")\r\n= \"click\"\r\nbutton(0)(\"name\")\r\n= \"今日歌曲\"\r\nbutton(0)(\"key\")\r\n= \"V1001\\_TODAY\\_MUSIC\"\r\n'增加第二个顶层按钮,这个按钮其实是一个菜单.\r\nbutton.Add(New\r\nJobject)\r\nbutton(1)(\"name\")\r\n= \"功能\"\r\nbutton(1)(\"sub\\_button\")\r\n= New Jarray\r\n'第二个顶层按钮的sub\\_button属性是一个数组,包括所有的子菜单按钮\r\nDim\r\nSubButton\r\nAs\r\nJArray\r\n=\r\nbutton(1)(\"sub\\_button\")\r\n'为方便后面的编码，将子菜单数组保存在变量SubButton中\r\n'增加第一个子菜单按钮\r\nSubButton.Add(New\r\nJobject)\r\nSubButton(0)(\"type\")\r\n= \"view\"\r\nSubButton(0)(\"name\")\r\n= \"搜索\"\r\nSubButton(0)(\"url\")\r\n= \"http://www.soso.com/\"\r\n'增加第二个子菜单按钮\r\nSubButton.Add(New\r\nJobject)\r\nSubButton(1)(\"type\")\r\n= \"view\"\r\nSubButton(1)(\"name\")\r\n= \"视频\"\r\nSubButton(1)(\"url\")\r\n= \"http://v.qq.com/\"\r\n'增加第三个子菜单按钮\r\nSubButton.Add(New\r\nJobject)\r\nSubButton(2)(\"type\")\r\n= \"click\"\r\nSubButton(2)(\"name\")\r\n= \"赞一下我们\"\r\nSubButton(2)(\"url\")\r\n= \"V1001\\_GOOD\"\r\nOutput.Show(mnu.ToString)\r\n'生成JSON字符串\r\n\r\n你也许觉得代码有点长，因为这是\"硬\"生成，但是自行用工具编写JSON数据非常繁琐，也很容易出错，多数时候我们会通过数据表输入菜单设置，然后用代码遍历数据表中的行自动生成JSON数据。\r\n\r\n实际上我在编写上面这段实例代码时，已经考虑到你未来的需要，你可以很轻松地将上面这段代码改为根据数据表中的行遍历生成，这在《微信接口》这一章会有介绍。\r\n\r\n## 压缩JSON\r\n\r\n压缩JSON\r\n\r\n\r\n**压缩JSON**\r\n\r\nCompressJson是Foxtable提供的一个用于压缩JSON字符串的函数，该函数的参数可以是一个JObject或JArray，也可以是一个JSON字符串。\r\n\r\n我们知道JObject等的ToString方法，输出的JSON字符串是经过格式化的，存在空格缩进和换行，以方便阅读。\r\n\r\n例如：\r\n\r\nDim\r\njson As\r\nString =\r\n\"{'touser':['zhansan', 'lisi'],'msgtype':'text'}\"\r\nDim\r\njo As\r\nJObject = JObject.Parse(json)\r\nOutput.Show(jo.ToSTring)\r\n\r\n输出结果为：\r\n\r\n{\r\n  \"touser\": [\r\n    \"zhansan\",\r\n    \"lisi\"\r\n  ],\r\n  \"msgtype\": \"text\"\r\n}\r\n\r\n如果用CompressJson函数输出，则会压缩掉多余的空格和换行，例如：\r\n\r\nDim\r\njson As\r\nString =\r\n\"{'touser':['zhansan', 'lisi'],'msgtype':'text'}\"\r\nDim\r\njo As\r\nJObject = JObject.Parse(json)\r\nOutput.Show(CompressJson(jo))\r\n\r\n输出结果为：\r\n\r\n{\"touser\":[\"zhansan\",\"lisi\"],\"msgtype\":\"text\"}\r\n\r\n如果你需要在数据库中存储JSON字符串，先用CompressJson压缩一下比较合适。\r\n\r\n## 关于引号\r\n\r\n单引号还是双引号\r\n\r\n\r\n**单引号还是双引号**\r\n\r\nJSON可以用单引号，也可以用双引号，例如：\r\n\r\nDim\r\njson As\r\nString =\r\n\"{'name':'李云龙','age':'36'}\"\r\n\r\n也可以写为：\r\n\r\nDim\r\njson As\r\nString =\r\n\"{\"\"name\"\":\"\"李云龙\"\",\"\"age\"\":\"\"36\"\"}\"\r\n\r\n为方便阅读，本文档很多地方采用了单引号编写JSON。\r\n\r\n但实际上双引号才是标准的写法，兼容性最好，例如微信使用的JSON，就必须是双引号的。\r\n\r\n所以自行编写JSON字符串的时候，最好全部使用双引号。\r\n\r\n好消息是，Newtonsoft.JSON生成的JSON都是双引号的，所以基本上我们无需为这个问题分心。\r\n\r\n## 解析XML\r\n\r\n解析XML\r\n\r\n\r\n**解析XML**\r\n\r\n现在互联网的数据传递，JSON的使用率已经远远超过XML，我也推荐大家使用JSON。\r\n\r\n但是微信有点奇葩，有的接口是XML，有的是JSON，开发者需要处理两种类型的数据，而且有时还需要转换。\r\n\r\n为了提高大家的学习和开发效率，我们增加了一个新的类型XObject，让大家可以将XML数据直接作为JSON数据进行解析。\r\n\r\nXObject和JObject在数据解析方面是完全相同的，可以说唯一差别就是类名不同，几乎不需要额外的学习就能掌握。\r\n\r\n如果你熟悉.net，可以直接用.net的相关类库进行XML的解析和生成，也一样的简单，效率会更高一些。\r\n\r\n至于普通用户，还是用XObject来得快捷方便。\r\n\r\n由于Foxtable和System.XML.Linq命名空间都有名为XObject的类，实际使用时为避免冲突，必须加上命名空间Foxtable，例如：\r\n\r\nDim\r\nxo As **Foxtable**.XObject\r\n\r\n\r\n**示例一**\r\n\r\n解析简单的XML。\r\n\r\n源XML数据结构：\r\n\r\n<XML>\r\n<to>George</to>\r\n<from>John</from>\r\n<content>Don't forget the meeting!</content>\r\n</XML>\r\n\r\n在命令窗口执行下面的代码：\r\n\r\nDim\r\nxml As\r\nString =\r\n\"<xml><to>George</to><from>John</from><content>Don't forget the\r\nmeeting!</content></xml>\"\r\nDim\r\nxo As\r\nFoxtable.XObject = Foxtable.XObject.Parse(xml)\r\nOutput.Show(xo(\"to\"))\r\nOutput.Show(xo(\"from\"))\r\nOutput.Show(xo(\"content\"))\r\n\r\n显示的结果为：\r\n\r\nGeorge\r\nJohn\r\nDon't forget the meeting!\r\n\r\n可以看到解析XML和解析JSON的方法几乎是一样的。\r\n\r\n**示例二**\r\n\r\n嵌套XML的解析，源XML数据结构：\r\n\r\n<XML>\r\n<公司>宏兴贸易</公司>\r\n<部门>\r\n<名称>销售部</名称>\r\n<员工>张三</员工>\r\n<员工>李四</员工>\r\n</部门>\r\n<部门>\r\n<名称>行政部</名称>\r\n<员工>王五</员工>\r\n<员工>赵六</员工>\r\n</部门>\r\n</XML>\r\n\r\n在命令窗口执行以下代码：\r\n\r\nDim\r\nxml As\r\nString =\r\n\"<xml><公司>宏兴贸易</公司><部门><名称>销售部</名称><员工>张三</员工><员工>李四</员工></部门>\"\r\n\r\nxml\r\n= xmL &\r\n\r\n\"<部门><名称>行政部</名称><员工>王五</员工><员工>赵六</员工></部门></xml>\"\r\nDim\r\nxo As\r\nFoxtable.XObject = Foxtable.XObject.Parse(xml)\r\nOutput.Show(xo(\"公司\"))\r\nFor\r\nEach bm\r\nAs JToken\r\nIn  xo(\"部门\")\r\n    Output.show(bm(\"名称\"))\r\n    For Each\r\nyg As\r\nJToken In\r\nbm(\"员工\")\r\n\r\nOutput.Show(yg)\r\n    Next\r\nNext\r\n\r\n显示的结果为：\r\n\r\n宏兴贸易\r\n销售部\r\n张三\r\n李四\r\n行政部\r\n王五\r\n赵六\r\n\r\n在解析XML的过程中，同一父节点之下的同名结点，会被解析为一个数组，为了验证这一点，可以在命令窗口执行：\r\n\r\nDim\r\nxml As\r\nString =\r\n\"<xml><公司>宏兴贸易</公司><部门><名称>销售部</名称><员工>张三</员工><员工>李四</员工></部门>\"\r\n\r\nxml =\r\nxmL &\r\n\r\n\"<部门><名称>行政部</名称><员工>王五</员工><员工>赵六</员工></部门></xml>\"\r\nDim\r\nxo As\r\nFoxtable.XObject = Foxtable.XObject.Parse(xml)\r\nOutput.Show(xo.ToString())\r\n\r\n显示的结果为：\r\n\r\n{\r\n  \"公司\":\r\n\"宏兴贸易\",\r\n  \"部门\":\r\n[\r\n    {\r\n      \"名称\":\r\n\"销售部\",\r\n      \"员工\":\r\n[\r\n        \"张三\",\r\n        \"李四\"\r\n      ]\r\n    },\r\n    {\r\n      \"名称\":\r\n\"行政部\",\r\n      \"员工\":\r\n[\r\n        \"王五\",\r\n        \"赵六\"\r\n      ]\r\n    }\r\n  ]\r\n}\r\n\r\n可以看到，部门和员工，都是以JSON数组的形式存在，其中部门是对象数组，员工是值数组。\r\n\r\n通过上面的代码，大家也可以看到将XML数据转为JSON数据是多么的简单。\r\n\r\n既然是数组，就可以用JArray进行解析：\r\n\r\nDim\r\nxml As\r\nString =\r\n\"<xml><公司>宏兴贸易</公司><部门><名称>销售部</名称><员工>张三</员工><员工>李四</员工></部门>\"\r\n\r\nxml =\r\nxmL &\r\n\r\n\"<部门><名称>行政部</名称><员工>王五</员工><员工>赵六</员工></部门></xml>\"\r\nDim\r\nxo As\r\nFoxtable.XObject = Foxtable.XObject.Parse(xml)\r\nOutput.Show(xo(\"公司\"))\r\nDim\r\nbms As\r\nJArray = xo(\"部门\")\r\nFor\r\ni As\r\nInteger = 0\r\nTo bms.Count\r\n- 1\r\n    Output.show(bms(i)(\"名称\"))\r\n    Dim\r\nygs As\r\nJArray = bms(i)(\"员工\")\r\n    For n\r\nAs Integer =\r\n0 To\r\nygs.Count -\r\n1\r\n          Output.Show(ygs(n))\r\n    Next\r\nNext\r\n\r\n当然用For Each语句处理起来更加简洁一些。\r\n\r\n我们可以直接用索引提取数据，例如第一个部门的第二个员工，可以表示为：\r\n\r\nDim\r\nnm As\r\nString = xo(\"部门\")(0)(\"员工\")(1)\r\n\r\n## 生成XML\r\n\r\n生成XML\r\n\r\n\r\n**生成XML**\r\n\r\n我们也可以用XObject生成XML，和用JObject生成JSON的方法是一回事，只是前者用ToXML方法，后者用ToString方法。\r\n\r\n示例一\r\n\r\n在命令窗口执行：\r\n\r\nDim\r\njo As\r\nNew\r\nFoxtable.XObject\r\njo(\"Name\")\r\n= \"张三\"\r\njo(\"Group\")\r\n= \"VIP\"\r\njo(\"Type\")\r\n= 2\r\njo(\"Default\")\r\n= False\r\nOutput.Show(jo.ToXML)\r\n\r\n显示的结果为：\r\n\r\n<XML><Name>张三</Name><Group>VIP</Group><Type>2</Type><Default>false</Default></XML>\r\n\r\n**示例二**生成嵌套的XML。\r\n\r\n在命令窗口执行：\r\n\r\nDim\r\nxo As\r\nNew Foxtable.XObject\r\nxo(\"name\")\r\n= \"李云龙\"\r\nxo(\"age\")\r\n= \"36\"\r\nxo(\"card\")\r\n= New JObject\r\nxo(\"card\")(\"bank\")\r\n= \"工行\"\r\nxo(\"card\")(\"account\")\r\n= \"12345678\"\r\nOutput.Show(xo.ToXML)\r\n\r\n显示的结果为：\r\n\r\n\r\n<xml><name>李云龙</name><age>36</age><card><bank>工行</bank><account>12345678</account></card></xml>\r\n\r\n**示例三**\r\n\r\n生成带数组的XML。\r\n\r\n在命令窗口执行：\r\n\r\nDim\r\njo As\r\nNew Foxtable.XObject\r\njo(\"Name\")\r\n= \"张三\"\r\njo(\"Group\")\r\n= \"VIP\"\r\njo(\"Roles\")\r\n= New Jarray(\"manager\",\"developer\")\r\n'定义数组\r\njo(\"Type\")\r\n= 2\r\nOutput.Show(jo.ToXML)\r\n\r\n显示的结果为：\r\n\r\n\r\n<XML><Name>张三</Name><Group>VIP</Group><Roles>manager</Roles><Roles>Developer</Roles><Type>2</Type></XML>\r\n\r\n**示例四**生成包括对象数组的XML。\r\n\r\n在命令窗口执行：\r\n\r\nDim\r\njo As\r\nNew Foxtable.XObject\r\nDim\r\nja As\r\nNew JArray\r\njo(\"dept\")\r\n= \"销售部\"\r\njo(\"mpr\")\r\n= \"赵刚\"\r\njo(\"staff\")\r\n=  ja '\r\nFor\r\ni As\r\nInteger = 1\r\nTo 2\r\n    Dim so\r\nAs New\r\nJObject\r\n\r\nIf\r\ni = 1\r\nThen\r\n        so(\"name\")\r\n= \"李云龙\"\r\n        so(\"age\")\r\n= 36\r\n    Else\r\n        so(\"name\")\r\n= \"黄晓明\"\r\n        so(\"age\")\r\n= 26\r\n    End If\r\n    ja.Add(so)\r\nNext\r\nOutput.Show(jo.ToXML)\r\n\r\n显示的结果为：\r\n\r\n<XML><dept>销售部</dept><mpr>赵刚</mpr><staff><name>李云龙</name><age>36</age></staff><staff><name>黄晓明</name><age>26</age></staff></XML>\r\n\r\n## CDATA结点\r\n\r\nCDATA结点\r\n\r\n\r\nCDATA结点\r\n\r\nXML的CDATA结点可以理解为数据结点，此类结点的内容会被原样不动解析，内容中的所有标记都会被忽略，也就是说CDATA结点可以包括任意特殊字符。\r\n\r\n微信接收方的消息都是XML结构，其中很多结点就是CDATA的：\r\n\r\n<xml>\r\n<ToUserName><![CDATA[gh\\_c78d6e907523]]></ToUserName>\r\n<FromUserName><![CDATA[o\\_0W1wG4xCDJTpdqlTfjSdVjlznQ]]></FromUserName>\r\n<CreateTime>1483933403</CreateTime>\r\n<MsgType><![CDATA[text]]></MsgType>\r\n<Content><![CDATA[测试文本]]></Content>\r\n<MsgId>6373445435737863016</MsgId>\r\n</xml>\r\n\r\n其中有4个结点是CDATA的，CDATA结点内容\r\n以“<![CDATA[”开始，以“]]>”结束。\r\n\r\n**解析CDATA结点**\r\n\r\n将上面的XML数据复制到剪贴板，在命令窗口执行下面的代码：\r\n\r\nDim\r\nxo As\r\nFoxtable.XObject =\r\nFoxtable.XObject.Parse(ClipBoard.GetText())\r\nOutput.Show(xo(\"ToUserName\"))\r\nOutput.Show(xo(\"FromUserName\"))\r\nOutput.Show(xo(\"CreateTime\"))\r\nOutput.Show(xo(\"MsgType\"))\r\nOutput.Show(xo(\"Content\"))\r\nOutput.Show(xo(\"MsgId\"))\r\n\r\n显示的结果为：\r\n\r\ngh\\_c78d6e907523\r\no\\_0W1wG4xCDJTpdqlTfjSdVjlznQ\r\n1483933403\r\ntext\r\n测试文本\r\n6373445435737863016\r\n\r\n可以看到CDATA结点的解析和普通结点是一样的。\r\n\r\n**增加CDATA结点**\r\n\r\nFoxtable.XObject的AddCDATA方法用于新增CDATA结点，此方法的语法为：\r\n\r\nAddCDATA(Key, Value)\r\n\r\nKey：  结点名称\r\nValue：结点值\r\n\r\n例如在命令窗口执行：\r\n\r\nDim\r\nxo As\r\nNew Foxtable.XObject\r\nxo(\"name\")\r\n= \"李云龙\"\r\nxo(\"age\")\r\n= 38\r\nxo.AddCDATA(\"remark\",\"1+\r\n2 <> 2\")\r\n\r\nxo(\"rank\")\r\n= \"少将\"\r\nOutput.Show(xo.ToXML)\r\n\r\n显示的结果为：\r\n\r\n<XML><name>李云龙</name><age>38</age><remark><![CDATA[1+ 2 <>\r\n2]]></remark><rank>少将</rank></XML>\n\nFile v2.0.5:references/10_更多接口.md\n\n# 更多接口\r\n\r\n\r\n## 阿里云全国物流查询接口\r\n\r\n阿里云全国物流查询接口\r\n\r\n\r\n**阿里云全国物流查询接口**\r\n\r\n利用Foxtable 2017，可以很方便第对第三方接口进行开发，实际上微信接口就是第三方接口。\r\n\r\n本节再提供一个微信之外的接口开发示例：阿里云全国物流查询接口\r\n\r\n此接口需要购买，不过非常便宜，在我写这个文档的时候，你只需花1分钱，就可以买100次的使用权：\r\n\r\n<https://market.aliyun.com/products/57126001/cmapi010996.html#sku=yuncode499600008>\r\n\r\n购买之后，获取AppCode进行开发。\r\n\r\n购买页面有接口使用说明，按照说明，我们开发出一下功能，每一个功能都只有四行代码，相比阿里云的示例，要简单得多。\r\n\r\n假定我们的AppCode为：7858a126750949e4a36371580f420413，快递单号为：929601675231\r\n\r\n**根据快递单号查询快速公司名**\r\n\r\n参考代码：\r\n\r\nDim\r\nhc As\r\nNew HttpClient(\"http://ali-deliver.showapi.com/fetchCom?nu=929601675231\")\r\nhc.Headers.Add(\"Authorization\",\"APPCODE\r\n\" &\r\n\"7858a126750949e4a36371580f420413\")\r\nDim\r\njo As\r\nJObject = Jobject.Parse(hc.GetData)\r\nOutput.Show(jo.ToString)\r\n\r\n**根据快递单号查询物流信息**\r\n\r\n参考代码：\r\n\r\nDim\r\nhc As\r\nNew HttpClient(\"http://ali-deliver.showapi.com/showapi\\_expInfo?com=shunfeng&nu=929601675231\")\r\nhc.Headers.Add(\"Authorization\",\"APPCODE\r\n\" &\r\n\"7858a126750949e4a36371580f420413\")\r\nDim\r\njo As\r\nJObject = Jobject.Parse(hc.GetData)\r\nOutput.Show(jo.ToString)\r\n\r\n所有代码返回的都是JSON数据，你可以对JSON数据进行进一步的处理，例如：\r\n\r\nDim\r\nhc As\r\nNew HttpClient(\"http://ali-deliver.showapi.com/showapi\\_expInfo?com=shunfeng&nu=929601675231\")\r\nhc.Headers.Add(\"Authorization\",\"APPCODE \" & \"7858a126750949e4a36371580f420413\")\r\nDim\r\njo As\r\nJObject = Jobject.Parse(hc.GetData)\r\nIf\r\njo(\"showapi\\_res\\_body\")(\"data\")\r\nIsNot Nothing\r\nThen\r\n    For Each\r\njt As\r\nJToken In\r\njo(\"showapi\\_res\\_body\")(\"data\")\r\n        Output.Show(jt(\"time\").ToString\r\n& \" | \"\r\n& jt(\"context\").ToString)\r\n    Next\r\nEnd\r\nIf\r\n\r\n显示的内容为：\r\n\r\n2017-03-02 10:24:37 | 快件在【江门江海集散中心】已装车，准备发往下一站\r\n2017-03-02 05:41:54 | 快件到达 【江门江海集散中心】\r\n2017-03-01 22:31:24 | 快件在【湛江麻章集散中心】已装车，准备发往 【江门江海集散中心】\r\n2017-03-01 22:24:37 | 快件到达 【湛江麻章集散中心】\r\n2017-03-01 20:02:01 | 快件在【湛江市赤坎文保北村营业点】已装车，准备发往 【湛江麻章集散中心】\r\n2017-03-01 19:49:28 | 顺丰速运 已收取快件\r\n\r\n**查询所有快递公司信息**\r\n\r\n参考代码：\r\n\r\nDim\r\nhc As\r\nNew HttpClient(\"http://ali-deliver.showapi.com/showapi\\_expressList?maxSize=500\")\r\nhc.Headers.Add(\"Authorization\",\"APPCODE\r\n\" &\r\n\"7858a126750949e4a36371580f420413\")\r\nDim\r\njo As\r\nJObject = Jobject.Parse(hc.GetData)\r\nOutput.Show(jo.ToString)\n\nFile v2.0.5:references/11_生成并发送PDF文件.md\n\n# 生成并发送PDF文件\r\n\r\n\r\n# 生成并发送PDF文件\r\n\r\n生成并发送PDF文件\r\n\r\n\r\n**生成并发送PDF文件**\r\n\r\n在Foxtable\r\n2025之前，要生成PDF文档发送给客户端，需要向通过Excel报表、Word报表或专业报表生成报表，然后另存或转换为PDF格式，效率很低，而且兼容性极差。\r\n\r\n从Foxtable\r\n2025开始，我们可以通过PDFCreator快速生成PDF文档。\r\n\r\n而且PDFCreator生成的PDF文档，无需保存为文件，可以直接发送到客户端，效率是原来的千百倍，而且不再有任何兼容性问题。\r\n\r\nHttpRequest事件为此专门增加了一个WritePDFCreator方法，其语法为：\r\n\r\nWritePDFCreator(Creator, FileName, Inline)\r\n\r\n|  |  |\r\n| --- | --- |\r\n| Creator | 要发送的PDFCreator |\r\n| FileName | 客户端浏览器下载此文件时使用的文件名 |\r\n| InLine | 可选参数，逻辑型，是否直接在浏览器显示PDF文件，默认为True，设为False将直接下载。 |\r\n\r\n**示例**\r\n\r\n将HttpRequest事件代码设置为：\r\n\r\nDim\r\npdc\r\nAs\r\n\r\nNew\r\nPDFCreator()\r\nDim\r\nrect\r\nAs\r\nRectangleF = pdc.PageRectangle()\r\nrect.Inflate( - 72, - 72)\r\n'\r\nDim\r\nfnt\r\nAs\r\n\r\nNew\r\nFont(\"微软雅黑\",\r\n12)\r\npdc.DrawString(\"Hello\r\nFoxtable!\",\r\nfnt, Brushes.Black, rect)\r\ne.WritePDFCreator(pdc,\r\n\r\n\"test.pdf\")\r\n\r\n现在客户端访问网页，可以瞬间得到一个名为\"test.pdf\"的PDF文件。\r\n\r\n你也可以转换成网页格式再发送给客户端，参考代码：\r\n\r\nDim\r\npdc\r\nAs\r\n\r\nNew\r\nPDFCreator()\r\nDim\r\nrect\r\nAs\r\nRectangleF = pdc.PageRectangle()\r\nrect.Inflate( - 72, - 72)\r\n'\r\nDim\r\nfnt\r\nAs\r\n\r\nNew\r\nFont(\"微软雅黑\",\r\n12)\r\npdc.DrawString(\"Hello\r\nFoxtable!\",\r\nfnt, Brushes.Black, rect)\r\nDim\r\ntmpPath\r\nAs\r\n\r\nString\r\n= ProjectPath &\r\n\r\n\"HttpTempFiles\\\"\r\nIf\r\nFileSys.FileExists(tmpPath) =\r\nFalse\r\n\r\nThen\r\n    FileSys.CreateDirectory(tmpPath)\r\nEnd\r\n\r\nIf\r\n\r\n\r\nDim\r\ntempFile\r\nAs\r\n\r\nString\r\n= tmpPath & Rand.NextString(10) &\r\n\".html\"\r\npdc.SaveToHtmL(tempFile)\r\ne.WriteFile(tempFile)\r\n\r\n提示;\r\n\r\n1、所有浏览器都支持直接显示PDF文件，所以一般没有必要转成HTML再发送。\r\n\r\n2、临时文件建议放在HttpTempFiles子目录中，方便系统自动清理。\n\nArchive v2.0.4: 20 files, 216353 bytes\n\nFiles: references/00_关于文档构成.md (2632b), references/01_关于移动开发.md (1112b), references/02_关于功能演示.md (492b), references/03_关于网页设计.md (542b), references/04_HTML入门.md (120974b), references/05_WeUI框架.md (320984b), references/06_扩展WeUI的功能.md (339b), references/07_客户端类.md (27069b), references/08_JSON解析.md (26750b), references/09_微信接口.md (278113b), references/10_更多接口.md (2978b), references/11_生成并发送PDF文件.md (2186b), references/12_生成并发送Word文件.md (1481b), references/13_用Excel报表生成网页.md (27468b), references/14_类型参考.md (19626b), references/15_异步编程.md (37254b), references/16_Web数据源.md (8037b), skill-card.md (2994b), SKILL.md (3339b), _meta.json (136b)\n\nFile v2.0.4:SKILL.md\n\n---\r\nname: \"狐表(Foxtable) 移动端开发\"\r\ndescription: \"狐表(Foxtable)移动端开发完整文档，涵盖HTML入门、WeUI框架、微信接口、客户端类、JSON解析、异步编程、Web数据源等，完整保留多层导航结构\"\r\nread_when:\r\n  - foxmobile\r\n  - foxtable 移动开发\r\n  - foxtable 手机\r\n  - foxtable 微信接口\r\n  - foxtable WeUI\r\n  - foxtable web开发\r\n  - foxtable HTML\r\n  - foxtable 客户端类\r\n  - foxtable JSON\r\n  - foxtable 异步编程\r\n  - foxtable web数据源\r\n  - 狐表 移动\r\n---\r\n\r\n# Foxtable 移动开发文档\r\n\r\n本技能包含狐表(Foxtable)移动端开发的完整帮助文档，从 CHM 文件转换而来，**完整保留了原始的多层导航结构（最深 5 层）**。Foxtable 移动开发用于基于 Foxtable 构建手机端管理系统，支持 B/S 架构的 Web 应用开发。\r\n\r\n## 何时使用\r\n\r\n当用户询问 Foxtable/狐表 的移动开发、Web开发、微信接口、WeUI框架、HTML页面开发、客户端类、JSON解析、异步编程、Web数据源等问题时，加载对应的参考文档进行回答。\r\n\r\n## 参考文档索引\r\n\r\n按章节组织，每个文件包含该章节下的全部页面内容（文件内用 `#`/`##`/`###`/`####`/`#####` 多层标题还原导航层级，可精确定位）：\r\n\r\n| 文件 | 章节 | 页面数 | 内容概要 |\r\n|------|------|--------|----------|\r\n| `references/00_关于文档构成.md` | 关于文档构成 | 1 | 文档结构说明 |\r\n| `references/01_关于移动开发.md` | 关于移动开发 | 1 | 移动开发概述 |\r\n| `references/02_关于功能演示.md` | 关于功能演示 | 1 | 功能演示说明 |\r\n| `references/03_关于网页设计.md` | 关于网页设计 | 1 | 网页设计原则 |\r\n| `references/04_HTML入门.md` | HTML入门 | 46 | HTML基础、服务器配置、Hello World |\r\n| `references/05_WeUI框架.md` | WeUI框架 | 109 | WeUI组件、样式、布局（核心章节） |\r\n| `references/06_扩展WeUI的功能.md` | 扩展WeUI | 1 | WeUI功能扩展 |\r\n| `references/07_客户端类.md` | 客户端类 | 14 | 客户端API与操作 |\r\n| `references/08_JSON解析.md` | JSON解析 | 9 | JSON数据处理 |\r\n| `references/09_微信接口.md` | 微信接口 | 76 | 微信公众号、支付、模板消息等（核心章节） |\r\n| `references/10_更多接口.md` | 更多接口 | 1 | 其他API接口 |\r\n| `references/11_生成并发送PDF文件.md` | PDF文件 | 1 | PDF生成与发送 |\r\n| `references/12_生成并发送Word文件.md` | Word文件 | 1 | Word生成与发送 |\r\n| `references/13_用Excel报表生成网页.md` | Excel报表网页 | 6 | Excel报表转网页 |\r\n| `references/14_类型参考.md` | 类型参考 | 10 | 数据类型速查 |\r\n| `references/15_异步编程.md` | 异步编程 | 14 | 异步操作与回调 |\r\n| `references/16_Web数据源.md` | Web数据源 | 3 | Web端数据源连接 |\r\n\r\n## 使用方式\r\n\r\n1. 根据用户问题判断属于哪个章节\r\n2. 读取对应的 references 文件\r\n3. 文件内按 `#`/`##`/`###` 等多层标题定位具体内容（导航层级已完整保留）\r\n4. WeUI框架(05)和微信接口(09)是两个核心章节\r\n\r\n## 文档来源\r\n\r\n原始文件: `E:\\foxtable\\Development\\foxmobile.chm` (6.6MB)\r\n转换时间: 2026-07-21\r\n原始页面数: 295 页 HTML\r\n导航层级: 最深 5 层，完整保留（无截断）\n\nFile v2.0.4:_meta.json\n\n{\n  \"ownerId\": \"kn77z6synmrxqwaha37s8r2b7h8a9k07\",\n  \"slug\": \"foxmobile-dev-doc\",\n  \"version\": \"2.0.4\",\n  \"publishedAt\": 1784621371907\n}\n\nFile v2.0.4:references/00_关于文档构成.md\n\n# 关于文档构成\r\n\r\n\r\n# 关于文档构成\r\n\r\n关于文档构成\r\n\r\n\r\n**关于文档构成**\r\n\r\n本文档是Foxtable的《高级开发指南》，是Foxtable主文档的补充，用于介绍部分Foxtable高级开发版特有的功能，例如网页设计入门、手机管理系统的开发，微信接入、三层架构设计和多线程编程等等。\r\n\r\n\r\n我们必须在已经掌握《使用指南》和《开发指南》的基础上，才能学习《高级开发指南》。\r\n\r\n本文档主要由七个部分组成\r\n\r\n* **HTML入门**\r\n  这一章介绍了如何开启一个HTTP服务，如果和用HTML生成网页，如何接收用户通过网页输入的数据，如何保存用户上传的文件等等。一般用户对于HTML稍作了解即可，因为实际开发的时候，我们一般会用内置的WeUI框架生成网页。\r\n* **WeUI框架**\r\n  这一张是重点，通过这一章，你会发现在Foxtable生成网页是多么的简单。\r\n  WeUI框架来自于腾讯，我们对其进行了一些扩展，增加了表格和AJAX功能，通过框架，我们可以用非常简单的代码生成专业效果的网页，本框架主要针对移动设备，虽然也能在PC上使用，但在手机上的效果更好。\r\n* **客户端类**\r\n\r\n  这一章介绍了客户端类HttpClient，Foxtable通过HttpClient和HTTP服务进行交互，包括第三方的HTTP服务。我们在进行微信开发的时候，实际上就是通过HttpClient调用腾讯提供的HTTP服务。\r\n* **JSON解析**\r\n\r\n  这一章介绍了JSON和XML数据的解析生成，互联网中的数据传递格式主要是JSON和XML，例如进行微信开发的时候，你会发现除了回调部分的数据是XML格式，其余都是JSON格式。\r\n  通过这一章，你会发现在Foxtable进行JSON和XML的解析与生成，实在是太轻松了。\r\n* **微信接口**\r\n\r\n  这一章介绍了如何在Foxtable进行微信开发，分别针对公众号和企业号进行讲述，基本上涵盖了所有微信常用接口，由于我们进行了适度的封装，微信开发变得前所未有的轻松。实际上不仅是微信开发，你可以用Foxtable完成任何第三方接口的开发，帮助就提供了一个阿里云快递查询接口的开发示例，只有短短的三行代码而已\r\n  。\r\n* **Web数据源**\r\n  Foxtable默认是两层结构，客户端直接连接数据库，这一章介绍如何使用Web数据源设计三层架构的系统，使得客户端不再直连数据库。\r\n* **异步编程**\r\n  这一节章介绍如何如何用异步编程同时处理多个任务，以提高服务器的负载能力。\n\nFile v2.0.4:references/01_关于移动开发.md\n\n# 关于移动开发\r\n\r\n\r\n# 关于移动开发\r\n\r\n关于移动开发\r\n\r\n\r\n**关于移动开发**\r\n\r\nFoxtable的高级开发版提供了移动开发功能。\r\n\r\nFoxtable的移动开发功能以B/S形式实现，内置手机网页自动生成功能，你不需要额外的学习，你可以完全不懂网页设计，也可以快速开发基于手机的企业管理系统，包括服务端的架设和网页的生成，全都可以在Foxtable中用最简单的方式完成，不仅简单到人人都能掌握，而且效率极高，相对于其它开发工具，代码节省了85%左右。\r\n\r\n此外高级开发版还提供了微信接入功能，用户可以直接通过微信输入、查询、统计和审核数据，还可以直接通过手机扫描二维码并将扫描结果发送到Foxtable。\r\n\r\n下面是Foxtable自动生成的一些手机网页。\r\n\r\n一个订单编辑页面：\r\n\r\n\r\n\r\n图片浏览和上传页面：\r\n\r\n\r\n\r\n输入界面很灵活：\r\n\r\n\r\n\r\n可以直接在手机端生成Excel报表：\r\n\r\n\r\n\r\n可以直接扫描条形码或者二维码输入数据：\r\n\r\n\r\n\r\n\r\n提供了多种输入组件：\n\nFile v2.0.4:references/02_关于功能演示.md\n\n# 关于功能演示\r\n\r\n\r\n# 关于功能演示\r\n\r\n关于功能演示\r\n\r\n\r\n**关于功能演示**\r\n\r\n你可以用微信扫描以下二维码，关注Foxtable的公众号：\r\n\r\n我们在这个公众号上做了一个简单的示例，进入公众号后，单击菜单“移动测试”可以进入这个示例。\r\n\r\n我们很难用一个例子演示完毕Foxtable的移动开发功能，只要你认真学习本文档，就能很轻松地用Foxtable设计出很专业的手机端管理系统。\n\nFile v2.0.4:references/03_关于网页设计.md\n\n# 关于网页设计\r\n\r\n\r\n# 关于网页设计\r\n\r\n关于网页设计\r\n\r\n\r\n**关于网页设计**\r\n\r\n在本文档的开始部分，我们会介绍一些网页设计方面的知识，让大家了解一下网页设计是怎么回事。\r\n\r\n实际上你可以完全不会网页设计，Foxtable内置了网页自动生成功能(通过扩展后的WeUI框架实现)，你不需要有任何的网页设计知识，一样可以设计出具备专业效果的网页来。\r\n\r\n例如你只需寥寥十来行代码，就可以设计出下面的网页来：\n\nFile v2.0.4:references/04_HTML入门.md\n\n# HTML入门\r\n\r\n\r\n## Hello World\r\n\r\nHello Word\r\n\r\n\r\n**Hello Word**\r\n\r\n我们先用一个经典的例子，来说明如何使用Foxtable实现http服务。\r\n\r\n首先是一个重要提示：要启动http服务，必须以管理员身份运行Foxtable。\r\n\r\n你可以在Windows的桌面上右击Foxtable的快捷方式，在快捷菜单中的单击属性命令，然后在兼容性页面中勾选“以管理员身份运行此程序”：\r\n\r\n启动Foxtable后，将下面的代码复制到命令窗口执行：\r\n\r\nHttpServer.Prefixes.Add(\"http://127.0.0.1/\")\r\nHttpServer.Start()\r\n\r\n然后在菜单的“管理项目”功能区，单击“网络监视器”，将“HttpRequest”事件的代码设置为：\r\n\r\ne.WriteString(\"Hello\r\nWorld\")\r\n\r\n现在打开你的浏览器，输入地址：http://127.0.0.1，会显示如下内容：\r\n\r\n是的，我们只用了3行代码，就建立了一个简单的http服务系统。\r\n\r\n通过下面的代码，可以关闭http服务：\r\n\r\nHttpServer.Close()\r\n\r\n现在为了接下来的测试方便，我们可以设计一个窗口，窗口有两个按钮，分别用于开启和关闭http服务：\r\n\r\n按钮的代码就是前面介绍的，就不重复了。\r\n\r\n注意每次开启新的服务之前，都必须关闭之前的服务，否则会无效或出现错误提示。\r\n\r\n## 服务器IP\r\n\r\n服务器IP\r\n\r\n\r\n**服务器IP**\r\n\r\n上一节我们搭建了一个简单的http服务，你只能在本机访问这个http服务，因为127.0.0.1表示的是本机IP。\r\n\r\n我们可以设置多个IP，假定服务器的局域网IP是：192.168.0.100，我们可以将开启服务按钮的代码改为：\r\n\r\nHttpServer.Prefixes.Add(\"http://127.0.0.1/\")\r\nHttpServer.Prefixes.Add(\"http://192.168.0.100/\")\r\nHttpServer.Start()\r\n\r\n这样局域网中的其他电脑，就能通过局域网IP来访问我们的http服务了：\r\n\r\n如果服务器有公网IP，一样可以加入，这样外网用户就能通过公网IP来访问我们的http服务。\r\n\r\n如果服务器有多个IP，可以用下面的代码来开启http服务：\r\n\r\nHttpServer.Prefixes.Add(\"http://\\*/\")\r\nHttpServer.Start()\r\n\r\n这样用户就可以通过指向服务器的任何一个IP来访问http服务，包括本机IP、局域网IP和公网IP。\r\n\r\n建议今后都采用以上代码来开启http服务，这样就无需因IP的变动而修改代码了。\r\n\r\n如果是公网，可以设置域名指向你的服务器，让用户通过域名来访问你的http服务。\r\n\r\n提示：为了测试方便，接下来所有例子，都将通过127.0.0.1来访问。\r\n\r\n## 服务器端口\r\n\r\n服务器端口\r\n\r\n\r\n**服务器端口**\r\n\r\n如果我们不指定端口，那么http服务使用的就是默认的端口80。\r\n\r\n下面的代码：\r\n\r\nHttpServer.Prefixes.Add(\"http://\\*/\")\r\nHttpServer.Start()\r\n\r\n等同于：\r\n\r\nHttpServer.Prefixes.Add(\"http://\\*:80/\")\r\nHttpServer.Start()\r\n\r\n有时，我们需要给自己的http服务更换端口。\r\n例如你公司已经有了一台web服务器，现在你需要利用这台服务器运行Foxtable的http服务，那么就不能使用80端口了，因为这样会和原来的web服务发生冲突。\r\n\r\n换个端口开启http服务很简单，例如：\r\n\r\nHttpServer.Prefixes.Add(\"http://\\*:32177/\")\r\nHttpServer.Start()\r\n\r\n现在你要访问这个http服务，地址必须加上端口号才行：\r\n\r\n为了方便，就下来的例子还是使用默认的端口80。\r\n\r\n## 增加入站规则\r\n\r\n入站规则\r\n\r\n\r\n**入站规则**\r\n\r\n如果你在另一台电脑访问我们新建立的http服务，很可能无法访问。\r\n\r\n通常这是防火墙造成的，我们可以新建一个入站规则，让防火墙允许客户端访问我们的http服务。\r\n\r\n以windows 10自带的防火墙为例，新建一个入站规则的步骤为：\r\n\r\n1、在Windows的控制面板，打开“Windows 防火墙”。\r\n\r\n2、在“Windows 防火墙”窗口，单击“高级设置”。\r\n\r\n3、在“高级设置”窗口，单击“入站规则”，然后在右侧单击“新建规则”。\r\n\r\n4、选择规则类型为“端口”：\r\n\r\n5、协议类型选择TCP，并输入你的http服务所采用的端口号：\r\n\r\n5、接下来选择允许接入：\r\n\r\n6、应用规则勾选所有：\r\n\r\n7、最后输入规则名称即可，名称可以随便取，最好方便识别。\r\n\r\n## 开启多个服务\r\n\r\n开启多个服务\r\n\r\n\r\n**开启多个服务**\r\n\r\n你可以一次开启多个http服务，例如：\r\n\r\nHttpServer.Prefixes.Add(\"http://\\*/\")\r\nHttpServer.Prefixes.Add(\"http://\\*:32177/\")\r\nHttpServer.Prefixes.Add(\"http://192.168.0.100:32188/\")\r\nHttpServer.Start()\r\n\r\n上面的代码开启了三个http服务，头两个服务可以通过指向服务器的任何ip来访问，例如：\r\n\r\nhttp://127.0.0.1/\r\nhttp://192.168.0.100/\r\n\r\nhttp://127.0.0.1:32177/\r\nhttp://192.168.0.100:32177/\r\n\r\n第三个服务则只能根据固定的IP地址访问：\r\n\r\nhttp://192.168.0.100:32188/\r\n\r\n重要提示：当你通过多个IP开启服务时，只要有一个IP错误，所有服务都是不能启动的。\r\n\r\n## 显示不同的内容\r\n\r\n显示不同的内容\r\n\r\n\r\n**显示不同的内容**\r\n\r\n在上一节中，我们开启了三个http服务，当我们分别访问这三个服务时，显示的内容都是“Hello Word”。\r\n显然，这不符合实际需要，真正的http服务应该能根据用户输入的不同地址，显示不同的内容。\r\n\r\nHttpRequest事件的e参数有很多，其中有三个e参数，用于获取用户访问路径的各部分。\r\n\r\nHost: 返回IP地址(或域名)\r\nPort：返回端口号\r\nPath：返回路径，含文件名。\r\n\r\n假定用户输入的访问路径是\"http://192.168.0.100:32188/fox/\",这三个e参数的返回值分别是：\r\n\r\nHost: 192.168.0.100\r\nPort: 32188\r\nPath: fox\r\n\r\n假定用户输入访问路径是\"http://192.168.0.100:32188/fox/china.htm\"，这三个e参数的返回值分别是：\r\n\r\nHost: 192.168.0.100\r\nPort: 32188\r\nPath: fox\\china.htm\r\n\r\n注意：\r\n\r\n1、e参数path中的路径分割符号是\"\\\"，不是\"/\"。\r\n2、尽量通过路径和文件名来区分用户的访问请求，不要占用太多端口，因为端口是有限的。\r\n\r\n现在就可以根据用户访问的不同端口、路径和和文件，甚至是不同的IP，来显示不同的内容，例如将HttpRequest的代码设置为：\r\n\r\nIf\r\ne.host =\r\n\"127.0.0.1\" Then\r\n    e.WriteString(\"你是本地用户\")\r\nElse\r\n    Select Case\r\ne.Port\r\n        Case 80\r\n            e.WriteString(\"Hello\r\nWorld\")\r\n\r\nCase\r\n32177\r\n            e.WriteString(\"你好,世界\")\r\n        Case 32188\r\n            If\r\ne.path = \"fox\"\r\nThen\r\n                e.WriteString(\"你好,狐表用户\")\r\n            ElseIf e.path\r\n= \"fox\\china.htm\" Then\r\n'注意这里的路径分隔符是\"\\\"\r\n                e.WriteString(\"你好,中国狐表用户\")\r\n\r\nElse\r\n                e.WriteString(\"你好,未找到网页\")\r\n            End If\r\n        Case\r\nElse\r\n            e.WriteString(\"你好,端口错误\")\r\n    End\r\nSelect\r\nEnd\r\nIf\r\n\r\n如果你通过地址“http://192.168.0.100:32188/fox”访问http服务，将显示:\r\n\r\n如果你通过地址\"http://192.168.0.100:32188/fox/china.htm\"访问http服务，将显示：\r\n\r\n## 关于WriteString\r\n\r\n关于WriteString\r\n\r\n\r\n**关于WriteString**\r\n\r\n在前面的例子中，在HttpReuest用下面的代码：\r\n\r\ne.WriteString(\"Hello\r\nWorld\")\r\n\r\n在浏览器中显示了下面的内容：\r\n\r\n需要注意的是，每次只能执行一次WriteString或WriteFile(后面会介绍)，例如下面的代码是无法正常运行的：\r\n\r\ne.WriteString(\"Hello\r\nWorld\")\r\ne.WriteString(\"Hello\r\nChina\")\r\n'这一行会出错\r\n\r\n需要将两个字符串合并为一个，再用WriteString写入浏览器，例如：\r\n\r\nDim\r\nrs As String = \"Hello World\"\r\nrs = rs\r\n& vbcrlf\r\n& \"Hello China\"\r\ne.WriteString(rs)\r\n\r\n在浏览器中显示的内容是：\r\n\r\n你肯定会奇怪，vbcrlf是回车换行，按理应该分两行显示，怎么上面的内容只显示了一行呢？\r\n这就是涉及到HTML标签的知识，在HTML中，连续的多个空格或换行，都会被显示为1个空格，而换行用<br/>表示。\r\n\r\n所以要分开两行显示，正确的HttpReuest事件代码是：\r\n\r\nDim\r\nrs As\r\nString =\r\n\"Hello World\"\r\nrs =\r\nrs &\r\n\"<br/>Hello China\"\r\ne.WriteString(rs)\r\n\r\n在浏览器显示的内容是：\r\n\r\n如果要分段显示文字，可以将段落放在<p>和</p>之间，例如将HttpReuest事件代码设置为：\r\n\r\nDim\r\nrs As\r\nString =\r\n\"<p>Foxtable首先是一个优秀的应用软件,你不需要编写任何代码,即可高效完成日常数据管理工作.</p>\"\r\nrs = rs\r\n&\r\n\"<p>同时Foxtable又是一个高效的.net平台开发工具,专门针对数据管理软件的开发作了大量的优化</p>\"\r\ne.WriteString(rs)\r\n\r\n在浏览器显示的结果为：\r\n\r\n用a标签可以插入超链接，例如将HttpReuest事件代码设置为：\r\n\r\nDim\r\nrs As\r\nString =\r\n\"<a href='http://www.foxtable.com/'>单击此处</a>访问Foxtable主页\"\r\ne.WriteString(rs)\r\n\r\n<a>标签的href属性指定链接地址，属性用单引号括起来，<a>和</a>之间为要显示的文本，上述代码在浏览器显示的结果为：\r\n\r\nHTML的标签有很多，我们并不打算一一介绍，教会大家HTML不是我们的任务，有人比我们做得更好，有兴趣的话，可以访问：\r\n\r\n<http://www.w3school.com.cn/html/index.asp>\r\n\r\n通常不超过半天时间就可以掌握了。\r\n\r\n## 关于网页设计\r\n\r\n关于网页设计\r\n\r\n\r\n**关于网页设计**\r\n\r\n上一节我们接触了一些常用的HTML标签，但我们不会有专门的章节来介绍HTML标签。\r\n因为我们并不打算教会大家网页设计，这不是我们的长处，已经有很多比我们更擅长的人在做这项工作。\r\n我只想告诉大家，如果你掌握了网页设计，你之前所有的知识，包括HTML、CSS、JavaScript和JQuery等等，在Foxtable都能继续派上用场。\r\n\r\n**可以不会网页设计**\r\n\r\n如果你完全不会网页设计，也没有关系，Foxtable内置了网页自动生成功能，不需要有任何的网页设计知识，一样可以设计出具备专业效果的网页来。\r\n\r\n例如你只需寥寥十来行代码，就可以设计出下面的网页来：\r\n\r\n如果你想提前了解一下，可以参考：[使用框架生成网页](0011.htm)\r\n\r\n**会网页设计更好**\r\n\r\n自动生成，意味着轻松，也意味着约束，如果你懂一些网页设计，你就可以突破这种约束，甚至可以完全放弃Foxtable的自动网页生成功能。\r\n\r\n如果你打算学习网页设计，我给你推荐一个网站：<http://www.w3school.com.cn/h.asp>\r\n\r\n对于一般用户来说，花一两天的时间掌握[HTML](http://www.w3school.com.cn/html/index.asp)、[HTML5](http://www.w3school.com.cn/html5/index.asp)和[CSS](http://www.w3school.com.cn/css/index.asp)，就足够使用了，如果想做一些动态的效果，可以再掌握一下[JavaScript](http://www.w3school.com.cn/js/index.asp)和[CSS3](http://www.w3school.com.cn/css3/index.asp)。\r\n\r\n## 使用StringBuilder\r\n\r\n使用StringBuilder\r\n\r\n\r\n**使用StringBuilder**\r\n\r\n前面的例子，我们是直接将字符串组合起来，然后写入客户端的浏览器：\r\n\r\nDim\r\nrs As\r\nString =\r\n\"Hello World\"\r\nrs =\r\nrs &\r\n\"<br/>Hello China\"\r\ne.WriteString(rs)\r\n\r\n如果要写入的内容比较多，这种方法是比较低效的，因为.net合并字符串的效率并不好。\r\n\r\n我们建议用[StringBuilder](2097.htm)来合并字符串，例如：\r\n\r\nDim\r\nsb As\r\nNew\r\nStringBuilder\r\nsb.AppendLine(\"Hello\r\nWorld\")\r\nsb.AppendLine(\"<br/>Hello\r\nChina\")\r\ne.WriteString(sb.ToString)\r\n\r\n生成的网页内容越多，使用[StringBuilder](2097.htm)的效率优势就越明显，今后例子都将采用这种方式。\r\n\r\n## 用HTML生成网页\r\n\r\n用HTML生成网页\r\n\r\n\r\n**用HTML生成网页**\r\n\r\n本节内容适合已经掌握HTML的用户，其实不管你是否已经掌握HTML，建议都学习一下，因为：真的很简单！\r\n\r\n现在我们将HttpRequest事件代码改为：\r\n\r\nDim\r\nsb As\r\nNew\r\nStringBuilder\r\nsb.AppendLine(\"<form\r\nenctype='multipart/form-data' method='post'\r\nid='form1'\r\nname='form1'>\")\r\nsb.AppendLine(\"产品:\r\n<input name='cp' id='cp'><br/><br/>\")\r\nsb.AppendLine(\"客户:\r\n<input name='kh' id='kh'><br/><br/>\")\r\nsb.AppendLine(\"雇员:\r\n<input name='gy' id='gy'><br/><br/>\")\r\nsb.AppendLine(\"单价:\r\n<input type='number' name='dj' id='dj'><br/><br/>\")\r\nsb.AppendLine(\"折扣:\r\n<input type='number' name='zk' id='zk' min='0' max='0.15' step='0.01'><br/><br/>\")\r\nsb.AppendLine(\"数量:\r\n<input type='number' name='sl' id='sl'><br/><br/>\")\r\nsb.AppendLine(\"日期:\r\n<input type='date' name='rq' id='rq'><br/><br/>\")\r\nsb.AppendLine(\"<input\r\nType='submit' name='Sumbit' id='Sumbit' value='确定'>\")\r\nsb.AppendLine(\"</form>\")\r\ne.WriteString(sb.ToString)\r\n\r\n我们用上面的代码生成了一个表单，在Chorme浏览器中的显示效果为：\r\n\r\n即使你完全不懂HTML，看懂上面的代码也没有问题，以下面这一行代码为例：\r\n\r\nsb.AppendLine(\"折扣:\r\n<input type='number' name='zk' id='zk' min='0' max='0.15' step='0.01'><br/><br/>\")\r\n\r\n表示增加一个输入框，输入类型为数字(type='number')，允许最小值为0(min='0')，允许最大值为0.15(max='0.15'),允许输入精度为0.01(step='0.01')，id为zk(id='zk')，name为zk(name='zk')。\r\n\r\n在不同的浏览器，上述代码生成的页面会有不同的显示效果，例如对于日期输入框，Chorme可以显示一个下拉\r\n日历供选择，而且你只能输入日期，对于IE来说，这个日期输入框和普通的文本输入框没有任何差别。\r\n\r\n教会你HTML不是我的任务，有人干这个我们擅长很多，如果你有兴趣，可以访问：<http://www.w3school.com.cn/html/index.asp>，通常不会超过半天时间就能基本掌握。\r\n\r\n## 不用StringBuilder\r\n\r\n不用StringBuilder\r\n\r\n\r\n**不用StringBuilder**\r\n\r\n本文档的网页绝大多数都是用StringBuilder的AppendLine组合合成，这是因为网页要和Foxtable的数据结合，用StringBuilder适合运行过程中动态合成。\r\n\r\n我们也可以直接发送，例如：\r\n\r\nDim\r\nhtml\r\nAs\r\n\r\nString\r\n=\r\n<![CDATA[\r\n<form enctype='multipart/form-data' method='post' id='form1' name='form1'>\r\n产品:\r\n<input name='cp' id='cp'><br/><br/>\r\n客户:\r\n<input name='kh' id='kh'><br/><br/>\r\n雇员:\r\n<input name='gy' id='gy'><br/><br/>\r\n单价:\r\n<input type='number' name='dj' id='dj'><br/><br/>\r\n折扣:\r\n<input type='number' name='zk' id='zk' min='0' max='0.15' step='0.01'><br/><br/>\r\n数量:\r\n<input type='number' name='sl' id='sl'><br/><br/>\r\n日期:\r\n<input type='date' name='rq' id='rq'><br/><br/>\r\n<input Type='submit' name='Sumbit' id='Sumbit' value='确定'>\r\n</form>\r\n]]>.Value\r\ne.WriteString(html)\r\n\r\n结果和上一节完全相同。\r\n\r\n请注意，第一行的<![CDATA[和最后一行的]]>.Value是语法要求，并不包括在网页内容中，切记。\r\n\r\n对于少量需要动态改变的内容，可以先用字符串的Replace方法查找替换，然后再发送。\r\n\r\n## 生成结构完整的网页\r\n\r\n生成结构完整的网页\r\n\r\n\r\n**生成结构完整的网页**\r\n\r\n在浏览器打开我们上一节生成的网页，右击，查看源代码，可以看到其代码为：\r\n\r\n没有head？ 没有body？ 如果你接触过HTML网页设计，你会知道这不是一个结构完整的网页。\r\n\r\n你也不要担心，正如我们所演示的，目前所有的浏览器，都能正确解释上面的代码。\r\n\r\n不过遵循规范总是没有错，我们来改进一下HttpRequst事件的代码：\r\n\r\n|  |\r\n| --- |\r\n| Dim html  As  String =  <![CDATA[  <!doctype html>  <html>  <head>  <meta charset='utf-8'>  <title>表单</title>  </head>  <body>  <form enctype='multipart/form-data' method='post' id='form1' name='form1'>  产品: <input name='cp' id='cp'><br/><br/>  客户: <input name='kh' id='kh'><br/><br/>  雇员: <input name='gy' id='gy'><br/><br/>  单价: <input Type='number' name='dj' id='dj'><br/><br/>  折扣: <input Type='number' name='zk' id='zk' min='0' max='0.15' step='0.01'><br/><br/>  数量: <input Type='number' name='sl' id='sl'><br/><br/>  日期: <input Type='date' name='rq' id='rq'><br/><br/>  <input Type='submit' name='Sumbit' id='Sumbit' value='确定'>  </form>  </body>  </html>  ]]>.Value  e.WriteString(html) |\r\n\r\n现在生成的就是一个结构完整的网页，在浏览器看到的代码是：\r\n\r\n## 我想显示一个图片\r\n\r\n我想显示一个图片\r\n\r\n\r\n**我想显示一个图片**本节内容适合已经掌握HTML的用户，其实不管你是否已经掌握HTML，建议你学习一下，因为：真的很简单！\r\n\r\n在D盘建立一个名为web的目录，复制一个图片文件到这个目录，假定图片文件的名称是abc.jpg。\r\n\r\n我们将HttpRequest事件的代码改为：\r\n\r\nDim\r\nsb As\r\nNew\r\nStringBuilder\r\nsb.Appendline(\"我想显示一个图片<br/>\")\r\nsb.AppendLine(\"<img\r\nsrc='abc.jpg'>\")\r\ne.WriteString(sb.ToString())\r\n\r\n在浏览器显示的结果为：\r\n\r\nimg标签用于显示图片，其src属性为要显示的图片文件名称。\r\n\r\n不过图片并没有显示，这是意料之中的，因为服务端并没有发送图片文件到浏览器。\r\n\r\n浏览器其实是分两步来显示这个网页的：\r\n\r\n1、首先获取网页内容：\r\n\r\n我想显示一个图片<br/>\r\n<img src='abc.jpg'>\r\n\r\n2、然后浏览器解析网页内容，发现要显示一个图片\"abc.jpg\"，如是又向服务器发送访问请求：\r\n\r\nhttp://127.0.0.1/abc.jpg\r\n\r\n要显示图片，服务器必须对这个访问请求对出响应，将后台的图片\"abc.jpg\"发送给客户端的浏览器，浏览器则显示收到的图片。\r\n\r\n实际上不单单是图片，网页中引用的任何文件，包括js、css等等，都会单独向服务器发出下载这个文件的请求，服务器必须对此做出响应。\r\n\r\n据此，我们修改HttpRequest事件代码为：\r\n\r\nDim\r\nfl As\r\nString =\r\n\"d:\\web\\\" &\r\ne.path '合成含路径的文件名\r\nIf\r\nfilesys.FileExists(fl) '如果是请求一个已经存在的文件\r\n    e.WriteFile(fl) '则发送此文件\r\nElse\r\n    Dim sb\r\nAs New\r\nStringBuilder\r\n    sb.Appendline(\"我想显示一个图片<br/><br/>\")\r\n    sb.AppendLine(\"<img\r\nsrc='abc.jpg'>\")\r\n    e.WriteString(sb.ToString())\r\nEnd\r\nIf\r\n\r\n现在浏览器可以正常显示图片了：\r\n\r\n提示：HttpRequest事件的WriteFile方法用于向客户端发送本地文件。\r\n\r\n**安全问题**\r\n\r\n上面的代码是不严谨的，因为用户可以下载\"d:\\web\"目录下的所有文件，包括非图片文件。\r\n\r\n我们需要进行一些判断，限制用户只能下载图片文件，为此可以将HttpRequest事件代码改为：\r\n\r\nDim\r\nfl As\r\nString =\r\n\"d:\\web\\\" &\r\ne.path\r\nIf\r\nfilesys.FileExists(fl) '如果是请求一个已经存在的文件\r\n    Dim idx\r\nAs Integer\r\n= fl.LastIndexOf(\".\")\r\n    Dim ext\r\nAs String\r\n= fl.SubString(idx)\r\n    Select Case\r\next\r\n        Case\r\n\".jpg\",\".gif\",\".png\",\".bmp\",\".wmf\"\r\n '只允许请求图片文件\r\n            e.WriteFile(fl)\r\n    End\r\nSelect\r\nElse\r\n    Dim sb\r\nAs New\r\nStringBuilder\r\n    sb.Appendline(\"我想显示一个图片<br/><br/>\")\r\n    sb.AppendLine(\"<img\r\nsrc='abc.jpg'>\")\r\n    e.WriteString(sb.ToString())\r\nEnd\r\nIf\r\n\r\n## 文件引用的路径问题\r\n\r\n文件引用的路径问题\r\n\r\n\r\n**文件引用的路径问题**\r\n\r\n对于稍具规模的系统，为便于维护，我们很少会将所有文件放在同一个文件夹中。\r\n\r\n那么某一文件夹中的网页，如何引用其他文件夹中的文件呢。\r\n\r\n**使用绝对路径**\r\n\r\n如果被引用文件的路径是固定的，那么使用绝对路径即可。\r\n\r\n假定\"d:\\web\"为网页根目录，图片文件\"abc.jpg\"位于该目录的\"images\"子目录下。\r\n那么在任何路径的网页，都可以通过路径\"/images/abc.jpg\"引用此图片，\"/\"表示根目录\"d:\\web\"，所以\"/images/abc.jpg\"就表示\"d:\\web\\images\\abc.jpg\"。\r\n\r\n**示例**\r\n\r\n现在将文件\"abc.jpg\"复制到目录\"d:\\web\\images\"下，然后将HttpRequest事件代码改为：\r\n\r\nDim\r\nfl As\r\nString = \"d:\\web\\\"\r\n& e.path\r\nIf\r\nfilesys.FileExists(fl)\r\n    Dim idx\r\nAs Integer =\r\nfl.LastIndexOf(\".\")\r\n    Dim ext\r\nAs String  =\r\nfl.SubString(idx)\r\n    Select Case\r\next\r\n        Case\r\n\".jpg\",\".gif\",\".png\",\".bmp\",\".wmf\"\r\n            e.WriteFile(fl)\r\n    End\r\nSelect\r\nElse\r\n    Dim sb\r\nAs New\r\nStringBuilder\r\n    sb.Appendline(\"我想显示一个图片<br/><br/>\")\r\n    Select Case\r\ne.path\r\n        Case \"a.htm\"\r\n            sb.AppendLine(\"<img\r\nsrc='/images/abc.jpg'>\")\r\n        Case \"sub1\\a.htm\"\r\n            sb.AppendLine(\"<img\r\nsrc='/images/abc.jpg'>\")\r\n         Case\r\n\"sub1\\sub2\\a.htm\"\r\n            sb.AppendLine(\"<img\r\nsrc='/images/abc.jpg'>\")\r\n    End Select\r\n    e.WriteString(sb.ToString())\r\nEnd\r\nIf\r\n\r\n现在你通过下面三个网页访问HttpServer：\r\n\r\nhttp://127.0.0.1/a.htm\r\nhttp://127.0.0.1/sub1/a.htm\r\nhttp://127.0.0.1/sub1/sub2/a.htm\r\n\r\n可以看到，三个不同路径的网页，使用相同的引用路径\"\"\\images\\abc.jpg\"\"，都可以正常显示图片\"d:\\web\\images\\abc.jpg\"。\r\n\r\n**使用相对路径**\r\n\r\n如果要使用相对路径，需要用到两个重要的符号，一个是表示网页所在的目录的\".\"，一个是表示父目录的\"..\"\r\n\r\n假定网页所在目录为\"d:\\web\"目录，图片文件位于该目录的images子目录下，也就是\"d:\\web\\images\"目录下，那么使用相对路径的代码为：\r\n\r\n<img src='./images/abc.jpg'>\r\n\r\n\".\"表示网页所在目录\"d:\\web\"，所以\"./images\"表示\"d:\\web\\images\"\r\n\r\n假定网页所在目录为\"d:\\web\\sale\"目录，图片文件位于\"d:\\web\\images\"目录下，\r\n那么使用相对路径的代码为：\r\n\r\n<img src='../images/abc.jpg'>\r\n\r\n\"..\"表示\"d:\\web\\sale\"的父目录，也就是\"d:\\web\"，\r\n所以\"../images\"表示\"d:\\web\\images\"。\r\n\r\n**示例**\r\n\r\n现在将文件\"abc.jpg\"复制到目录\"d:\\web\\images\"下，然后将HttpRequest事件代码改为：\r\n\r\nDim\r\nfl As\r\nString =\r\n\"d:\\web\\\" &\r\ne.path\r\nIf\r\nfilesys.FileExists(fl)\r\n    Dim idx\r\nAs Integer\r\n= fl.LastIndexOf(\".\")\r\n    Dim ext\r\nAs String\r\n= fl.SubString(idx)\r\n    Select Case\r\next\r\n        Case\r\n\".jpg\",\".gif\",\".png\",\".bmp\",\".wmf\"\r\n            e.WriteFile(fl)\r\n    End\r\nSelect\r\nElse\r\n    Dim sb\r\nAs New\r\nStringBuilder\r\n    sb.Appendline(\"我想显示一个图片<br/><br/>\")\r\n    Select\r\nCase e.path\r\n        Case\r\n\"a.htm\"\r\n            sb.AppendLine(\"<img\r\nsrc='.\\images\\abc.jpg'>\")\r\n        Case \"sub1\\a.htm\"\r\n            sb.AppendLine(\"<img\r\nsrc='..\\images\\abc.jpg'>\")\r\n         Case\r\n\"sub1\\sub2\\a.htm\"\r\n            sb.AppendLine(\"<img\r\nsrc='..\\..\\images\\abc.jpg'>\")\r\n    End Select\r\n    e.WriteString(sb.ToString())\r\nEnd\r\nIf\r\n\r\n现在你通过下面三个网页访问HttpServer：\r\n\r\nhttp://127.0.0.1/a.htm\r\nhttp://127.0.0.1/sub1/a.htm\r\nhttp://127.0.0.1/sub1/sub2/a.htm\r\n\r\n都可以正常显示\"d:\\web\\images\"的图片\"abc.jpg\".\r\n\r\n## 使用JavaScript文件\r\n\r\n使用JavaScript文件\r\n\r\n\r\n**使用JavaScript文件**\r\n\r\n本节内容要使用到JavaScript。\r\n\r\n即使您没有掌握JavaScript，也没有关系，因为真的很简单。\r\n\r\n假定在以下网页中，希望输入单价、折扣和数量后，能即时计算出金额：\r\n\r\n要完成这个任务，就需要使用Javascript来编写脚本了。\r\n\r\n**知识准备**\r\n\r\n下面会对本节要使用的JavaScript知识做一个简单的介绍。\r\n\r\n教会你JavaScript，并不是我们的任务，\r\n如果你有兴趣进行进一步的了解，可以访问：<http://www.w3school.com.cn/b.asp>\r\n\r\n我个人建议你花上一两天时间，通过上述网页，掌握一下JavaScript的基础知识。\r\n\r\n首先是一个重要的提示：JavaScript是区分大小写的，例如calc和Calc是两个不同的变量或函数，逻辑值是true和false，写成True和False，也是不行的。\r\n\r\n**1、如何引用表单元素**\r\n\r\n我们在设计表单的时候，需要给输入框指定id属性和name属性，例如：\r\n\r\nsb.appendLine(\"单价:\r\n<input Type='number' name='dj' id='dj'>\")\r\n\r\n表示定义了一个输入框用于输入单价，这个输入框的id和name都是\"dj\"，id和name属性可以设置为不同的值，除非有特殊需要，一般设置为相同的值即可。\r\n\r\n我们可以在js代码中直接通过id属性引用这个输入框，例如：\r\n\r\ndj.value\r\n\r\n表示单价输入框的值，这是一种简写方式，使用起来很方便。\r\n\r\n还有一种更为标准的写法，例如下面的代码，同样是表示单价输入框的值：\r\n\r\ndocument.getElementById('dj').value\r\n\r\n前一种方法更为简洁，但后一种方法更为标准，多数编程书籍使用的都是后一种方法，本帮助文件两种都用。\r\n\r\n**2、网页事件**\r\n\r\n网页中所有元素都是有事件的，例如单击按钮，会触发按钮的onclick事件，修改输入框的内容，会触发输入框的onchange事件。\r\n\r\n我们可以在设计网页的时候，指定事件触发后要执行的代码，例如：\r\n\r\nsb.appendLine(\"单价:\r\n<input Type='number' name='dj' id='dj' onchange='calc()'>\")\r\n\r\n\r\n表示修改单价输入框的内容后，执行calc函数，注意函数名是区分大小写的，括号也是不能省略的。\r\n\r\n**3、在哪里写代码**\r\n\r\nJavaScript代码一般放在单独的文本文件中，后缀名为js，你可以直接用记事本编写js代码，我个人喜欢结合nodepad++和Dreamweaver编写，前者可以让我更方便地分析代码结构，后者可以帮我检查出语法错误。\r\n\r\n编写好的js代码一般放在某个子目录下，例如本帮助文件所有的js代码，都放在\"d:\\web\\lib\"目录下。\r\n\r\n编写好的js代码，并不会自动自动生效，我们在设计网页的时候，还需要将编写好的js代码文件，引入到网页中，例如：\r\n\r\nsb.appendline(\"<script src='./lib/calc.js'></script>\")\r\n\r\n表示将lib子目录下的calc.js文件引入到网页中。\r\n\r\n有了上面的知识，我们就能很轻松地完成自动计算金额的任务了。\r\n\r\n**设计步骤**\r\n\r\n1、在\"d:\\web\"目录下，建立一个子目录lib，在这个子目录中新建一个文本文件，文件名为\"calc.js\"，文件内容为：\r\n\r\nfunction calc(){\r\n    je.value = dj.value \\* sl.value \\* (1 - zk.value);\r\n}\r\n\r\n上述的代码定义了一个名为calc的函数，用于根据单价(dj)、数量(sl)和折扣(zk)的值，计算出金额。\r\n\r\n2、然后修改HttpRequest事件的代码，在有变动的地方，我加上了注释：\r\n\r\nDim\r\nfl As\r\nString = \"d:\\web\\\"\r\n& e.path\r\nIf\r\nfilesys.FileExists(fl)\r\n\r\nDim idx\r\nAs Integer =\r\nfl.LastIndexOf(\".\")\r\n\r\nDim ext\r\nAs String  =\r\nfl.SubString(idx)\r\n\r\nSelect Case\r\next\r\n\r\nCase \".jpg\",\".gif\",\".png\",\".bmp\",\".wmf\",\".js\"\r\n'这里加上了js扩展名\r\n\r\ne.WriteFile(fl)\r\n\r\nEnd\r\nSelect\r\nElse\r\n\r\nDim\r\nhtml\r\nAs\r\n\r\nString\r\n=\r\n<![CDATA[\r\n    <!doctype html>\r\n    <html>\r\n    <head>\r\n    <meta charset='utf-8'>\r\n    <title>表单</title>\r\n    </head>\r\n    <body>\r\n    <form enctype='multipart/form-data' method='post' id='form1'\r\nname='form1'>\r\n    产品:\r\n<input name='cp' id='cp'><br/><br/>\r\n    客户:\r\n<input name='kh' id='kh'><br/><br/>\r\n    雇员:\r\n<input name='gy' id='gy'><br/><br/>\r\n    单价:\r\n<input Type='number' name='dj' id='dj' onchange='calc()'><br/><br/>\r\n    折扣:\r\n<input Type='number' name='zk' id='zk' step='0.01' onchange='calc()'><br/><br/>\r\n    数量:\r\n<input Type='number' name='sl' id='sl' onchange='calc()'><br/><br/>\r\n    金额:\r\n<input Type='number' name='je' id='je' readonly><br/><br/>\r\n    日期:\r\n<input Type='date' name='rq' id='rq'><br/><br/>\r\n    <input Type='submit' name='Sumbit' id='Sumbit' value='确定'>\r\n    </form>\r\n    <script src='./lib/calc.js'></script>\r\n    </body>\r\n    </html>\r\n    ]]>.Value\r\n    e.WriteString(html)\r\nEnd If\r\n\r\n现在输入数量、单价和折扣，金额就会自动计算得出了：\r\n\r\n## 使用CSS文件\r\n\r\n使用CSS文件\r\n\r\n\r\n**使用CSS文件**\r\n\r\n本节内容适合已经掌握CSS的用户。\r\n\r\n同样，教会你CSS不是我们的任务，有人干这个我们擅长很多，如果有兴趣，可以访问：<http://www.w3school.com.cn/css/index.asp>，通常不会超过半天时间就能基本掌握。\r\n\r\n使用CSS文件和使用js文件的方法一样，只是CSS文件通常在head块引入，js通常在body块的结束位置引入。\r\n\r\n**一个例子**\r\n\r\n1、在\"d:\\web\"目录下，建立一个子目录css，在这个子目录中新建一个文本文件，文件名为文件名为“test.css”，文件内容为：\r\n\r\n.rd{color:red;}\r\n.gr{color:green;}\r\n\r\n2、然后修改HttpRequest事件的代码，在有变化的地方，我加上了注释：\r\n\r\nDim\r\nfl As\r\nString =\r\n\"d:\\web\\\" &\r\ne.path\r\nIf\r\nfilesys.FileExists(fl)\r\n    Dim idx\r\nAs Integer\r\n= fl.LastIndexOf(\".\")\r\n    Dim ext\r\nAs String\r\n= fl.SubString(idx)\r\n    Select Case\r\next\r\n        Case\r\n\".jpg\",\".gif\",\".png\",\".bmp\",\".wmf\",\".js\",\".css\"\r\n'这里加上了css扩展名\r\n            e.WriteFile(fl)\r\n    End\r\nSelect\r\nElse\r\n\r\nDim\r\nhtml\r\nAs\r\n\r\nString\r\n=\r\n<![CDATA[\r\n    <!doctype html>\r\n    <html>\r\n    <head>\r\n    <meta charset='utf-8'>\r\n    <title>CSS测试</title>\r\n    <link rel='stylesheet' href='./css/test.css'/>\r\n    </head>\r\n    <body>\r\n    <p class='rd'>This Is some text. This Is some text.</p>\r\n    <p class='gr'>This Is some text. This Is some text.</p>\r\n    </body>\r\n    </html>\r\n    ]]>.Value\r\n    e.WriteString(html)\r\nEnd\r\nIf\r\n\r\n在浏览器的显示结果为：\r\n\r\n## 使用第三方库\r\n\r\n使用第三方库\r\n\r\n\r\n**使用第三方库**\r\n\r\n一般用户可忽略本节内容。\r\n\r\n本节内容适合已经掌握JavaScript和CSS，希望使用第三方JavaScript库的用户。\r\n\r\n使用第三方的Javascript库和使用本地库并没有差别，下面是一个使用jQuery库的例子。\r\n\r\n1、在\"d:\\web\"目录下，建立一个子目录lib，在这个子目录中新建一个文本文件，文件名为\"test.js\"，文件内容为：\r\n\r\n$(document).ready(function(){\r\n    $('button').click(function(){\r\n        $('div').animate({left:'250px'});\r\n    });\r\n});\r\n\r\n2、HttpRequest事件代码设置为，在有变化的地方，我加上了注释：\r\n\r\nDim\r\nfl As\r\nString =\r\n\"d:\\web\\\" &\r\ne.path\r\nIf filesys.FileExists(fl)\r\n    Dim idx\r\nAs Integer\r\n= fl.LastIndexOf(\".\")\r\n    Dim ext\r\nAs String\r\n= fl.SubString(idx)\r\n    Select Case\r\next\r\n        Case\r\n\".jpg\",\".gif\",\".png\",\".bmp\",\".wmf\",\".js\",\".css\"\r\n\r\n            e.WriteFile(fl)\r\n    End\r\nSelect\r\nElse\r\n\r\nDim\r\nhtml\r\nAs\r\n\r\nString\r\n=\r\n<![CDATA[\r\n    <!doctype html>\r\n    <html>\r\n    <head>\r\n    <meta charset='utf-8'>\r\n    <title>jQuery测试</title>\r\n    <script src='https://libs.baidu.com/jquery/1.10.2/jquery.min.js'></script>\r\n    <script src='./lib/test.js'></script>\r\n    </head>\r\n    <body>\r\n    <button>开始动画</button><br/><br/>\r\n    <div style='background:#98bf21;height:100px;width:100px;position:absolute;'>\r\n    </body>\r\n    </html>\r\n    ]]>.Value\r\n\r\ne.WriteString(html)\r\nEnd\r\nIf\r\n\r\n在浏览器显示的结果如下，单击“开始动画”按钮，绿色方框会移动：\r\n\r\n## 使用设计好的网页\r\n\r\n使用设计好的网页\r\n\r\n\r\n**使用设计好的网页**\r\n\r\n前面的例子，我们都是用Foxtable动态合成网页。\r\n\r\n动态合成的好处是：网页是“活”的，同样的页面，根据不同的用户、数据和业务逻辑，显示内容和结构可以完全不同。\r\n\r\n我们也可以用第三方网页设计工具事先设计好网页，由Foxtable负责将网页发送给用户浏览器，代码很简单。\r\n\r\n例如你将设计好的网页，放在\"d:\\web\"目录及其子目录下，包括图片、js文件、css文件等等。\r\n\r\n然后将HttpRequest事件代码设置为：\r\n\r\nDim\r\nfl As\r\nString =\r\n\"d:\\web\\\" &\r\ne.path\r\nIf\r\nfilesys.FileExists(fl)\r\n    Dim idx\r\nAs Integer\r\n= fl.LastIndexOf(\".\")\r\n    Dim ext\r\nAs String\r\n= fl.SubString(idx)\r\n    Select Case\r\next\r\n        Case\r\n\".jpg\",\".gif\",\".png\",\".bmp\",\".wmf\",\".js\",\".css\"\r\n,\".html\",\".htm\"\r\n            e.WriteFile(fl)\r\n    End\r\nSelect\r\nEnd\r\nIf\r\n\r\n不到10行代码，这可能是史上最简单的架设http服务的方法。\r\n不，这不是最简单的，很快你会看到真正简单的http服务架设方法，只需三行代码。\r\n\r\n**静态与动态**\r\n\r\n如果网页需要和动态的数据以及业务逻辑相结合，就用代码动态生成；如果网页基本不变化，就事先设计好网页，也就是静态网页，以提高效率。\r\n\r\n同一个系统，可以同时有静态网页和动态网页，除非你明确告诉客户，否则客户并不会感受到这些页面什么不同。\r\n\r\n例如：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"\"\r\n        e.WriteString(\"这是根目录下的默认页面,是动态生成的\")\r\n    Case \"order.htm\"\r\n        e.WriteString(\"这是根目录下的Order.htm,这个文件并不存在,是自动生成的\")\r\n    Case \"sale\"\r\n        e.WriteString(\"这是根目录的sale子目录下的默认页面,是动态生成的\")\r\n    Case \"sale\\add.htm\"\r\n        e.WriteString(\"这是根目录的sale子目录下的add.htm,这个文件并不存在,是自动生成的\")\r\n    Case Else\r\n        Dim\r\nfl As\r\nString = \"d:\\web\\\"\r\n& e.Path\r\n        If\r\nfilesys.FileExists(fl)\r\n            Dim idx\r\nAs Integer\r\n= fl.LastIndexOf(\".\")\r\n            Dim ext\r\nAs String\r\n= fl.SubString(idx)\r\n            Select\r\nCase ext\r\n                Case\r\n\".jpg\",\".gif\",\".png\",\".bmp\",\".wmf\",\".js\",\".css\"\r\n,\".html\",\".htm\"\r\n                    e.WriteFile(fl)\r\n            End\r\nSelect\r\n        Else\r\n\r\ne.WriteString(\"好奇怪,你访问的页面不存在!\")\r\n        End\r\nIf\r\nEnd\r\nSelect\r\n\r\n有个小问题需要注意一下，e.path的路径分割符是\\\"，不是\"/\"。\r\n\r\n实际上，很难严格区分动态网页和动态网页，因为就是动态生成的网页，也需要使用不少静态的文件，例如图片、js、css等等。\r\n\r\n## 再谈WriteString\r\n\r\n再谈WriteString\r\n\r\n\r\n**再谈WriteString**\r\n\r\n**指定内容类型**\r\n\r\n前面讲到了通过WriteFile发送设计好的JS、CSS和HTML文件。\r\n\r\n如果文件内容很小，那么可以直接通过WriteString发送，通过第二个参数指定内容类型。\r\n\r\n例如：\r\n\r\ne.WriteString(\"function\r\nabc(){alert('你好！');}\",\r\n\r\n\"text/javascript\")\r\n\r\n**发送长文本**\r\n\r\n本文档有很多网页使用StringBuilder的AppendLine方法拼接，这是因为网页要和Foxtable的数据结合，用StringBuilder适合运行过程中动态合成。\r\n\r\n如果不需要拼接，就可以直接发送整个网页，例如：\r\n\r\nDim\r\nhtml\r\n\r\nAs\r\n\r\n\r\nString\r\n=\r\n\r\n<![CDATA[\r\n<form enctype='multipart/form-data' method='post' id='form1' name='form1'>\r\n产品:\r\n<input name='cp' id='cp'><br/><br/>\r\n客户:\r\n<input name='kh' id='kh'><br/><br/>\r\n雇员:\r\n<input name='gy' id='gy'><br/><br/>\r\n单价:\r\n<input type='number' name='dj' id='dj'><br/><br/>\r\n折扣:\r\n<input type='number' name='zk' id='zk' min='0' max='0.15' step='0.01'><br/><br/>\r\n数量:\r\n<input type='number' name='sl' id='sl'><br/><br/>\r\n日期:\r\n<input type='date' name='rq' id='rq'><br/><br/>\r\n<input Type='submit' name='Sumbit' id='Sumbit' value='确定'>\r\n</form>\r\n]]>.Value\r\ne.WriteString(html)\r\n\r\n请注意：\r\n\r\n1、第一行的<![CDATA[和最后一行的]]>.Value是语法要求，并不包括在网页内容中，切记。\r\n\r\n2、对于少量需要动态改变的内容，可以先用字符串的Replace方法查找替换，然后再发送，对于需要大量替换的，还是用StringBuilder替换会更高效一些。\r\n\r\n3、默认的内容类型就是\"text/html\"，所以这里可以省略WriteString的第二个参数。\r\n\r\n## 网页出现乱码啦\r\n\r\n网页出现乱码啦\r\n\r\n\r\n**网页出现乱码啦**\r\n\r\n我用古老的FrontPage中文版设计了一个网页，这个网页很简单，就一行文字：\r\n\r\n我喜欢用Foxtable开发管理软件\r\n\r\n我将这个网页复制到\"d:\\web\"目录下，通过HttpRequest将这个网页发送到客户端的浏览器上，意外出现了，中文全部变成了乱码：\r\n\r\n如果我们在本机双击这个文件，浏览器是能正常显示中文的，说明网页本身是没有问题的：\r\n\r\n为了查明原因，我用Nodepad++（也可以用记事本）打开这个网页，看到的源代码是：\r\n\r\n现在出现中文乱码的原因清楚了，因为HttpRequest并不会检查要发送网页的编码格式，默认一律采用utf-8编码格式读取网页内容，然后发送到用户浏览器。\r\n\r\n为解决这个问题，我们可以修改HttpRequest事件代码：\r\n\r\nDim\r\nfl As\r\nString = \"d:\\web\\\"\r\n& e.Path\r\nIf\r\nfilesys.FileExists(fl)\r\n    Dim idx\r\nAs Integer =\r\nfl.LastIndexOf(\".\")\r\n    Dim ext\r\nAs String  =\r\nfl.SubString(idx)\r\n    Select Case\r\next\r\n        Case\r\n\".jpg\",\".gif\",\".png\",\".bmp\",\".wmf\",\".js\",\".css\"\r\n,\".html\",\".htm\",\".doc\",\".docx\",\".xls\",\".xlsx\",\".pdf\"\r\n            e.ResponseEncoding\r\n= \"gb2312\"\r\n'设置网页编码为gb2312\r\n            e.WriteFile(fl)\r\n    End\r\nSelect\r\nElse\r\n    e.WriteString(\"好奇怪,你访问的页面不存在!\")\r\nEnd\r\nIf\r\n\r\n现在浏览器能正常显示中文了：\r\n\r\nHttpRequest的e参数ResponseEncoding，用于设置向客户端发送文本性质的内容时，所采用的编码格式。\r\n\r\n修改网页编码为utf-8格式\r\n\r\n你也可以不修改HttpRequest事件代码，使用utf-8编码保存网页即可，包括js和css文件，\r\n我们都建议采用utf-8编码。\r\n新的网页设计工具，多数默认编码就是utf-8格式。\r\n如果已经有其他编码格式的网页，需要改为utf-8编码，以上面的文件为例，步骤为：\r\n\r\n1、用记事本打开这个网页，找到charset=gb2312，改为charset=utf-8\r\n2、不要直接保存，在文件菜单单击\"另存为\"，在另存窗口选择编码格式为\"utf-8\"，然后单击\"确定\"按钮保存即可。\r\n\r\n## 接收表单数据\r\n\r\n接收表单数据\r\n\r\n\r\n**接收表单数据**\r\n\r\n多数时候，用户是通过表单输入数据的，表单类似于Foxtable的窗口。\r\n\r\n**定义表单**\r\n\r\n表单用<form>标签开始，用</form>标签结束，中间是各种输入元素。\r\n下面是一个表单的定义，表单名为form1，数据提交方式为post，数据接收页面是accept.htm:\r\n\r\n<form action='accept.htm' enctype='multipart/form-data' method='post' id='form1' name='form1'>\r\n\r\n</form>\r\n\r\n提示：表单的action属性是一个网页地址，用户输入完成后单击提交按钮，会将表单数据提交到这个网页。\r\n\r\n**Values属性**\r\n\r\nHttpRequest事件e参数有个Values属性，这是一个字典，包括所有用户提交的数据，键为输入元素的name属性，值就是输入元素的值。\r\n\r\n**一个例子**\r\n\r\n我们下面用一个例子看看Foxtable是如何定义表单和接收表单数据的，将HttpRequest事件代码设置为：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"input.htm\"\r\n\r\nDim\r\nhtml\r\nAs\r\n\r\nString\r\n=\r\n<![CDATA[\r\n        <form action='accept.htm' enctype='multipart/form-data' method='post'\r\nid='form1' name='form1'>\r\n        产品:\r\n<input name='cp' id='cp'><br/><br/>\r\n        客户:\r\n<input name='kh' id='kh'><br/><br/>\r\n        单价:\r\n<input type='number' name='dj' id='dj'><br/><br/>\r\n        数量:\r\n<input type='number' name='sl' id='sl'><br/><br/>\r\n        日期:\r\n<input type='date' name='rq' id='rq'><br/><br/>\r\n        密码:\r\n<input type='password' name='mm' id='mm'><br/><br/>\r\n        支付方式:\r\n<br/>\r\n        <input type='radio' name='fs' id='fs1' value = '支付宝'\r\nchecked>支付宝\r\n        <input type='radio' name='fs' id='fs2' value = '微信'>微信\r\n        <input type='radio' name='fs' id='fs3' value = '微信'>网银<br/><br/>\r\n        会员:\r\n<input type='checkbox' name='hy' id='hy'><br/><br/>\r\n        <input type='submit' name='sumbit' id='sumbit' value='提交'>\r\n        <input type='reset' name='reset' id='reset' value='重置'>\r\n        <input type='button' name='foxtble' id='foxtable' value='Foxtable主页'\r\nonclick='location=\"http://www.foxtable.com\"'>\r\n        </form>\r\n        ]]>.Value\r\n        e.WriteString(html)\r\n    Case \"accept.htm\"\r\n        Dim\r\nsb As New\r\nStringBuilder\r\n        sb.AppendLine(\"接收到的数据有:<br/><br/>\")\r\n        For Each\r\nkey As\r\nString In\r\ne.Values.Keys\r\n            sb.AppendLine(key\r\n& \":\"\r\n& e.Values(key)\r\n& \"<br/>\")\r\n        Next\r\n        e.WriteString(sb.ToString)\r\nEnd\r\nSelect\r\n\r\n这个代码生成了两个页面，第一个页面input.htm是一个表单，用于输入数据，这个表单展示了最常用的输入类型：\r\n\r\n用户单击提交按钮后，会将输入的数据提交到第二个页面accept.htm，该页面将收到的数据回写到客户端浏览器：\r\n\r\n建议你以后开发系统的时候，都先用上面的方法，实际测试一下表单所提交的数据内容和格式。\r\n\r\n需要注意的是，只有已经输入数据的输入框，以及已经勾选的radio和checbox，才会被提交到服务器，也就是说：\r\n\r\n1、没有数据的空白输入框，不会提交到服务器。\r\n2、没有勾选的radio和checkbox，也不会提交到服务器。\r\n\r\n## 在表单存储标记数据\r\n\r\n在表单插入标记数据\r\n\r\n\r\n**在表单插入标记数据**\r\n\r\n我们在生成表单的时候，可能需要在表单插入一些标记数据。\r\n\r\n例如你需要根据一个现有订单，来生成一个表单，那么就需要在生成的表单中存储主键值，这样用户编辑完成，将编辑结果提交到后台时，Foxtable才能知道用户编辑的是哪一个订单。\r\n\r\n我们可以增加一个隐藏的文本框，将这个文本框的值设置为主键，例如：\r\n\r\n<input name='identify' id='identify' value='1' **hidden**>\r\n\r\nvalue属性指定了这个文本框的默认值为1，加上hidden属性使得这个文本框在表单中处于隐藏状态，当用户提交表单数据到后台时，这个文本框的值会和其他输入框的值一起提交到后台\r\n，Foxtable就知道用户编辑的是主键为1的订单。\r\n\r\n我们通常不会专门介绍HTML的知识，但是这个方法对于我们今后设计应用系统很重要，所以用专门的一小节介绍一下。\r\n\r\n## 另一种数据提交方式\r\n\r\n另一种数据提交方式\r\n\r\n\r\n**另一种数据提交方式**\r\n\r\n除了前面介绍的通过表单提交数据，我们还可以通过URL地址提交数据，格式为：\r\n\r\nhttp://网页地址/?键1=值1&键2=值2&键3=值3\r\n\r\n**示例**\r\n\r\n将HttpRequest事件代码设置为：\r\n\r\nSelect\r\nCase e.Path\r\n     Case \"test.htm\"\r\n        Dim\r\nsb As New\r\nStringBuilder\r\n        sb.AppendLine(\"接收到的数据有:<br/><br/>\")\r\n        For Each\r\nkey As\r\nString In\r\ne.Values.Keys\r\n            sb.AppendLine(key\r\n& \":\"\r\n& e.Values(key)\r\n& \"<br/>\")\r\n        Next\r\n        e.WriteString(sb.ToString)\r\nEnd\r\nSelect\r\n\r\n然后在浏览器中输入地址：\r\n\r\nhttp://127.0.0.01/test.htm?product=foxtable&price=4688&count=2\r\n\r\n可以看到Foxtable已经正确提取出URL网址中包括的数据：\r\n\r\n这种数据提交方式，通常称为get方式，通过表单提交的方式，通常称为post方式。\r\n\r\n当然你可以将表单的method属性设置为get,这样表单也可以采用get方式提交数据，但是没有必要，因为post方式更安全、更方便，一次可以提交更多的数据。\r\n\r\n但是get方式也有自己的优势，就是轻量级，通常用于提交一些事先约定好的、不需要用户输入的数据。\r\n\r\n**混合Get和Post方式**\r\n\r\n其实Get和Post两种方式是可以混合使用的，例如我们将HttpRequest事件代码设置为：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"input.htm\"\r\n        Dim sb\r\nAs New\r\nStringBuilder\r\n\r\nsb.AppendLine(\"<form\r\nenctype='multipart/form-data' action='accept.htm?id=1&page=10' method='post'\r\nid='form1'\r\nname='form1'>\")\r\n        sb.AppendLine(\"产品:\r\n<input name='cp' id='cp'><br/><br/>\")\r\n        sb.AppendLine(\"客户:\r\n<input name='kh' id='kh'><br/><br/>\")\r\n        sb.AppendLine(\"数量:\r\n<input type='number' name='sl' id='sl'><br/><br/>\")\r\n        sb.AppendLine(\"日期:\r\n<input type='date' name='rq' id='rq'><br/><br/>\")\r\n        sb.AppendLine(\"<input\r\ntype='submit' name='sumbit' id='sumbit' value='提交'>\")\r\n        sb.AppendLine(\"<input\r\ntype='reset' name='reset' id='reset' value='重置'>\")\r\n         sb.AppendLine(\"</form>\")\r\n        e.WriteString(sb.ToString)\r\n    Case \"accept.htm\"\r\n        Dim sb\r\nAs New\r\nStringBuilder\r\n        sb.AppendLine(\"接收到的数据有:<br/><br/>\")\r\n        For Each\r\nkey As\r\nString In\r\ne.Values.Keys\r\n            sb.AppendLine(key\r\n& \":\"\r\n& e.Values(key)\r\n& \"<br/>\")\r\n        Next\r\n        e.WriteString(sb.ToString)\r\nEnd\r\nSelect\r\n\r\n上述代码中，我们定义的表单的代码为：\r\n\r\n<form action='accept.htm?id=1&page=10' method='post'\r\nname='form1'>\r\n\r\n用户输入完成单击提交按钮后，会将输入结果提交到地址\"accept.htm?id=1&page=10\"，这个地址包括id和page两个值。\r\n\r\n所以当用户按下图所示输入数据：\r\n\r\n然后单击提交按钮，提交到后台后，显示的内容会包括url地址中的内容，以及用户输入的内容：\r\n\r\n## Values、GetValues和PostValues\r\n\r\nValues、GetValues和PostValues\r\n\r\n\r\n**Values、GetValues和PostValues**\r\n\r\n前面已经说过，不管是通过GET方式提交，还是通过POST方式提交，客户端提交的所有数据都包括在Values字典中。\r\n\r\nHttpReqquest事件还有两个字典属性，他们是Values的子集：\r\n\r\nGetValues： 字典，仅包括通过GET方式提交的数据。\r\nPostValues：字典，仅包括通过POST方式提交的数据。\r\n\r\n例如我们将HttpRequest事件代码设置为：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"input.htm\"\r\n\r\nDim\r\nhtml\r\nAs\r\n\r\nString\r\n=\r\n<![CDATA[\r\n        <form enctype='multipart/form-data' action='accept.htm?id=1&page=10'\r\nmethod='post' id='form1' name='form1'>\r\n        产品:\r\n<input name='cp' id='cp'><br/><br/>\r\n        客户:\r\n<input name='kh' id='kh'><br/><br/>\r\n        数量:\r\n<input type='number' name='sl' id='sl'><br/><br/>\r\n        日期:\r\n<input type='date' name='rq' id='rq'><br/><br/>\r\n        <input type='submit' name='sumbit' id='sumbit' value='提交'>\r\n        <input type='reset' name='reset' id='reset' value='重置'>\r\n        </form>\r\n        ]]>.Value\r\n        e.WriteString(html)\r\n    Case \"accept.htm\"\r\n        Dim\r\nsb As New\r\nStringBuilder\r\n        sb.AppendLine(\"通过GET方式提交的数据:<br/><br/>\")\r\n        For Each\r\nkey As\r\nString In\r\ne.GetValues.Keys\r\n            sb.AppendLine(key\r\n& \":\"\r\n& e.GetValues(key)\r\n& \"<br/>\")\r\n        Next\r\n        sb.AppendLine(\"<br/><br/>通过POST方式提交的数据:<br/><br/>\")\r\n        For Each\r\nkey As\r\nString In\r\ne.PostValues.Keys\r\n            sb.AppendLine(key\r\n& \":\"\r\n& e.PostValues(key)\r\n& \"<br/>\")\r\n        Next\r\n        e.WriteString(sb.ToString)\r\nEnd\r\nSelect\r\n\r\n上述代码中，我们定义的表单的代码为：\r\n\r\n<form enctype='multipart/form-data' action='accept.htm?id=1&page=10'\r\nmethod='post' id='form1' name='form1'>\r\n\r\n用户输入完成单击提交按钮后，会将输入结果提交到地址\"accept.htm?id=1&page=10\"，这个地址包括id和page两个值。\r\n\r\n当用户按下图所示输入数据：\r\n\r\n然后单击提交按钮，提交到后台后，可以看到分别显示GET和POST方式提交的数据：\r\n\r\n## 简单的数据统计\r\n\r\n简单的数据统计\r\n\r\n\r\n**简单的数据统计**\r\n\r\n**本节任务**\r\n\r\n我们希望让用户通过下面的网页输入年月：\r\n\r\n单击“开始统计”，由Foxtable统计出该月各产品的销售数量，并显示在用户的浏览器上：\r\n\r\n**知识准备**\r\n\r\n用户是通过表单(form)输入数据的，这是我们本节要用到的一个表单：\r\n\r\n第1行代码为：\r\n\r\n<form action='showtj.htm' enctype='multipart/form-data'\r\nmethod='post' id='form1' name='form1'>\r\n\r\n表示用户单击确定按钮后，将结果提交到后台的showtj.htm网页处理，这个表单的名称为form1，数据提交方式为post\r\n\r\n再看第3行代码：\r\n\r\n年: <input type='number' name='year' id='year' min='2000' max='2018'><br/><br/>\r\n\r\n表示这是一个数值输入框，输入值的范围在2000到2018之间，这个输入框的名称（name）为\"year\"，我们必须给输入框指定name属性，因为后台的Foxtable要通过name属性取得输入值。\r\n\r\n服务端的HttpRequest事件通过e参数PostValues获取用户输入的值，这是一个字典，键为输入框的name属性，值就是用户所输入的值，例如在HttpRequest事件\r\n获取用户通过上述表单输入的年份的代码为：\r\n\r\nDim\r\ny As Integer\r\n= 0\r\n'将输入的年转换为整数，因为有的浏览器数值输入框也能输入字符!\r\nInteger.TryParse(e.PostValues(\"year\"),\r\ny)\r\nIf y = 0\r\nThen\r\n     e.WriteString(\"请输入年!\")\r\nEnd\r\nIf\r\n\r\n**完整代码**\r\n\r\n下面是HttpRequest事件的完整代码，代码不长，却完成了生成输入页面、获取用户输入\r\n内容、统计数据，以及呈现统计结果等四大任务：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"saletj.htm\"\r\n        Dim sb\r\nAs New\r\nStringBuilder\r\n\r\nsb.AppendLine(\"<form\r\nenctype='multipart/form-data' action='showtj.htm' method='post'\r\nid='form1'\r\nname='form1'>\")\r\n        sb.AppendLine(\"请输入要统计的年月:</br></br>\")\r\n        sb.AppendLine(\"年:\r\n<input type='number' name='year' id='year' min='1999' max='2018'><br/><br/>\")\r\n        sb.AppendLine(\"月:\r\n<input type='number' name='month' id='month' min='1' max='12'><br/><br/>\")\r\n        sb.AppendLine(\"<input\r\nType='submit' name='Sumbit' id='Sumbit' value='开始统计'>\")\r\n        sb.AppendLine(\"</form>\")\r\n        e.WriteString(sb.ToString)\r\n    Case \"showtj.htm\"\r\n\r\nDim y\r\nAs Integer =\r\n0\r\n        Dim m\r\nAs Integer =\r\n0\r\n\r\nInteger.TryParse(e.PostValues(\"year\"),\r\ny)\r\n'获取用户输入的\r\n年\r\n        Integer.TryParse(e.PostValues(\"month\"),\r\nm)\r\n'获取用户输入的月\r\n        If y\r\n= 0 OrElse\r\nm = 0\r\nThen\r\n            e.WriteString(\"请输入正确的年月!\")\r\n        Else\r\n            '根据输入的年月,统计各产品的销售数量\r\n            Dim g\r\nAs New\r\nSQLGroupTableBuilder(\"统计表1\",\r\n\"订单\")\r\n'这里使用后台统计,如果数据已经全部加载可以直接用GroupTableBuilder\r\n\r\n'g.ConnectionName\r\n= \"数据源\" '外部数据表的话要指定数据源名称\r\n            g.Groups.AddDef(\"产品\")\r\n            g.Totals.AddDef(\"数量\")\r\n            g.VerticalTotal\r\n= True\r\n            g.Filter\r\n= \"Year(日期)\r\n= \"\r\n& y\r\n&\r\n\" And Month(日期)\r\n= \"\r\n& m\r\n'后台统计才可以使用这种表达式的哦\r\n            Dim dt\r\nAs DataTable\r\n= g.Build(False)\r\n\r\n\r\n'将统计结果输出到用户浏览器\r\n            Dim sb\r\nAs New\r\nStringBuilder\r\n            sb.AppendLine(y\r\n& \"年\"\r\n& m\r\n& \"月各产品销售数量: <br/><br/>\")\r\n            For Each\r\ndr As\r\nDataRow In\r\ndt.DataRows\r\n                sb.AppendLine(dr(\"产品\")\r\n& \":\"\r\n& dr(\"数量\")\r\n& \"<br/>\")\r\n            Next\r\n            e.WriteString(sb.ToString)\r\n        End\r\nIf\r\nEnd\r\nSelect\r\n\r\n## 可能会出现性能问题\r\n\r\n可能会出现性能问题\r\n\r\n\r\n**可能会出现性能问题**\r\n\r\n一般用户可以忽略本节内容。\r\n\r\n上一节我们完成了一个简单的数据交互任务：\r\n让用户通过网页输入年月，后台根据用户输入的年月，统计出该月各产品的销售数量，并显示在用户浏览器上。\r\n\r\n不管是输入页面\"saletj.htm\"，还是显示统计结果的页面\"showtj.htm\"，其实都是不存在的，是后台动态生成的，是虚的。\r\n\r\n动态生成网页的优势是灵活，但是也有一个劣势，就是性能。\r\n\r\n以上一节的例子为例，每次统计的耗时和数据量是成正比的，如果用户每次访问都重新进行一次统计，且用户量和数据量都比较大，那么就必须考虑服务器的负担问题了。\r\n\r\n要减轻服务器的负担，我们可以这样考虑：\r\n\r\n1、本月的销售数据在不断变化中，适合每次访问都重新统计，而之前月份的销售数据已经固定，没有必要重复统计。\r\n\r\n2、所以我们可以将之前月份的统计结果保存为一个网页，这样以后访问时，无需再次进行统计\r\n，直接发送已经生成的网页即可。\r\n\r\n3、至于本月销售数据，当然每次都重新统计是最好的，但实际上用户统计本月数据的概率是最大的，每次重新统计，也可能导致服务器负担过重。\r\n\r\n4、所以对于本月统计，我们一样可以将统计结果保存为一个网页，用户每次访问时，判断这个网页的生成时间，如果没有超过1小时，就直接发送这个网页，否则重新进行统计。\r\n\r\n5、为方便管理，可以在\"d:\\web\"目录下，建立一个子目录\"temp\"，用于存放这些临时生成的网页。\r\n\r\n这是修改后的HttpRequest事件的代码，要正常运行这段代码，请在\"d:\\web\"目录下，建立一个子目录\"temp\"：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"saletj.htm\"\r\n        Dim sb\r\nAs New\r\nStringBuilder\r\n\r\nsb.AppendLine(\"<form\r\nenctype='multipart/form-data' action='showtj.htm' method='post'\r\nid='form1'\r\nname='form1'>\")\r\n        sb.AppendLine(\"请输入要统计的年月:</br></br>\")\r\n        sb.AppendLine(\"年:\r\n<input type='number' name='year' id='year' min='1999' max='2018'><br/><br/>\")\r\n        sb.AppendLine(\"月:\r\n<input type='number' name='month' id='month' min='1' max='12'><br/><br/>\")\r\n        sb.AppendLine(\"<input\r\nType='submit' name='Sumbit' id='Sumbit' value='开始统计'>\")\r\n        sb.AppendLine(\"</form>\")\r\n        e.WriteString(sb.ToString)\r\n    Case \"showtj.htm\"\r\n        Dim y\r\nAs Integer =\r\n0\r\n        Dim m\r\nAs Integer =\r\n0\r\n\r\nInteger.TryParse(e.PostValues(\"year\"),\r\ny)'获取用户输入的年\r\n        Integer.TryParse(e.PostValues(\"month\"),\r\nm)\r\n'获取用户输入的月\r\n        If y\r\n= 0 OrElse\r\nm = 0\r\nThen\r\n            e.WriteString(\"请输入正确的年月!\")\r\n        Else\r\n            Dim\r\nfl As String\r\n= \"d:\\web\\temp\\\" &\r\ny &\r\nm &\r\n\".htm\"\r\n'合成文件名\r\n            If FileSys.FileExists(fl)\r\nThen\r\n'如果文件存在\r\n                Dim\r\nifo As new\r\nFileInfo(fl)\r\n\r\n'如果不是本月,或者文件最近一次修改时间在1个小时内,则发送文件后返回\r\n\r\nIf\r\ny <> Date.Today.Year\r\nOrElse m <>\r\nDate.Today.Month\r\nOrElse (Date.Now\r\n- Ifo.LastWriteTime).TotalHours\r\n< 1\r\n                    e.WriteFile(fl)\r\n                    Return\r\n                End\r\nIf\r\n            End\r\nIf\r\n            '根据输入的年月,统计各产品的销售数量\r\n            Dim g\r\nAs New\r\nSQLGroupTableBuilder(\"统计表1\",\r\n\"订单\")\r\n'这里使用后台统计,如果数据已经全部加载可以直接用GroupTableBuilder\r\n\r\n\r\n'g.ConnectionName = \"数据源\"\r\n'外部数据表的话要指定数据源名称\r\n            g.Groups.AddDef(\"产品\")\r\n            g.Totals.AddDef(\"数量\")\r\n            g.VerticalTotal\r\n= True\r\n            g.Filter\r\n= \"Year(日期)\r\n= \"\r\n& y\r\n&\r\n\" And Month(日期)\r\n= \"\r\n& m\r\n'后台统计才可以使用这种表达式的哦\r\n            Dim dt\r\nAs DataTable\r\n= g.Build(False)\r\n\r\n'统计数据,将结果保存为网页,然后发送这个网页\r\n            Dim sb\r\nAs New\r\nStringBuilder\r\n            sb.AppendLine(y\r\n& \"年\"\r\n& m\r\n& \"月各产品销售数量: <br/><br/>\")\r\n            For Each\r\ndr As\r\nDataRow In\r\ndt.DataRows\r\n                sb.AppendLine(dr(\"产品\")\r\n& \":\"\r\n& dr(\"数量\")\r\n& \"<br/>\")\r\n            Next\r\n            sb.AppendLine(\"<br/>\")\r\n            sb.AppendLine(\"统计时间:\"\r\n& Date.Now)\r\n'在网页显示统计时间,提醒用户这可能不是最新的结果\r\n            FileSys.WriteAllText(fl,\r\nsb.ToString,\r\nFalse, Encoding.UTF8)\r\n'记得用UTF8格式保存网页\r\n            e.WriteFile(fl)\r\n'发送保存的网页\r\n        End\r\nIf\r\nEnd\r\nSelect\r\n\r\n如果之前月份的销售数据也有调整的可能，导致之前月份的统计结果也会变化，只是变化的概率和频率不高，那么你可以调整代码，检测之前月份统计文件的创建时间，如果超过1天\r\n（具体时长根据需要调整），就重新统计。\r\n\r\n## 一个页面完成一个任务\r\n\r\n一个页面完成一个任务\r\n\r\n\r\n**一个页面完成一个任务**\r\n\r\n上一节的统计任务，我们使用了两个页面，页面\"saletj.htm\"用于输入统计年月，页面\"showtj.htm\"用于显示统计结果。\r\n\r\n如果你愿意的话，也可以只用一个页面\"saletj.htm\"来完成这个统计任务，从代码维护角度而言，我更愿意用一个页面处理一个任务。\r\n\r\n**知识准备**\r\n\r\n我们已经知道，HttpRequest事件有个e参数PostValues，用于获取用户通过表单提交的数据。\r\n我们可以用一个隐藏的输入框，在这个输入框填入约定的数据，HttpRequest根据这个输入框的值，来执行相应的操作。\r\n\r\n例如：\r\n\r\n<input name='tj' id='tj' value='按年月统计' hidden>\"\r\n\r\n这里创建了一个名为\"tj\"的输入框，默认值是\"按年月统计\"，这个输入框加上了hidden属性，所以在页面中并不会显示，用户不会感觉有什么变化。\r\n\r\n在HttpRequest通过下面的代码，判断如果提交的页面名为tj的输入框，且他的值是\"按年月统计\"，那么就进行统计并显示统计结果，否则生成年月输入页面：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"saletj.htm\"\r\n        If\r\ne.PostValues.ContainsKey(\"tj\") AndAlso\r\ne.PostValues(\"tj\") = \"按年月统计\"\r\nThen\r\n              '统计数据，并显示统计结果\r\n\r\n        Else\r\n              '生成年月录入界面\r\n        End\r\nIf\r\nEnd\r\nSelect\r\n\r\n我们也可以用更简单的方式来区分两个页面：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"saletj.htm\"\r\n        If\r\ne.PostValues.Count\r\n> 0\r\nThen\r\n\r\n'统计数据，并显示统计结果\r\n\r\n        Else\r\n              '生成年月录入界面\r\n        End\r\nIf\r\nEnd\r\nSelect\r\n\r\n原理很简单，如果是直接访问saletj.htm，那么PostValues为空，其Count属性等于0，如果是通过表单的“开始统计”按钮访问saletj.htm，那么PostValues就会包括用户输入的值，其Count属性就会大于0。\r\n\r\n最后别忘记修改表单定义语句，该语句之前是：\r\n\r\n<form action='showtj.htm' enctype='multipart/form-data'\r\nmethod='post' id='form1' name='form1'>\r\n\r\n我们需要改为：\r\n\r\n<form action='saletj.htm' enctype='multipart/form-data' method='post' id='form1'\r\nname='form1'>\r\n\r\n**完整的代码**\r\n\r\n下面是HttpRequest事件的完整代码，只使用一个页面完成和上一节完全相同的任务：\r\n\r\nSelect\r\nCase e.Path\r\n    Case \"saletj.htm\"\r\n        If\r\n\r\ne.PostValues.ContainsKey(\"tj\") AndAlso e.PostValues(\"tj\") = \"按年月统计\"\r\nThen\r\n            Dim\r\ny As\r\nInteger = 0\r\n            Dim\r\nm As\r\nInteger = 0\r\n\r\nInteger.TryParse(e.PostValues(\"year\"),\r\ny)\r\n'获取用户输入的年\r\n\r\nInteger.TryParse(e.PostValues(\"month\"),\r\nm)\r\n'获取用户输入的月\r\n            If\r\ny = 0\r\nOrElse m = 0\r\nThen\r\n                e.WriteString(\"请输入正确的年月!\")\r\n            Else\r\n                Dim\r\nfl As\r\nString =\r\n\"d:\\web\\temp\\\" &\r\ny &\r\nm &\r\n\".htm\"\r\n'合成文件名\r\n                If\r\nFileSys.FileExists(fl)\r\nThen\r\n'如果文件存在\r\n                    Dim\r\nifo As\r\nnew FileInfo(fl)\r\n\r\n\r\n\r\n'如果不是本月,或者文件创建时间在1个小时内,则发送文件后返回\r\n\r\nIf\r\ny <> Date.Today.Year\r\nOrElse m <>\r\nDate.Today.Month\r\nOrElse (Date.Now\r\n- Ifo.LastWriteTime).TotalHours\r\n< 1\r\n                        e.WriteFile(fl)\r\n                        Return\r\n                    End\r\nIf\r\n                End\r\nIf\r\n\r\n '根据输入的年月,统计各产品的销售数量\r\n                Dim\r\ng As New\r\nSQLGroupTableBuilder(\"统计表1\",\r\n\"订单\")\r\n'这里使用后台统计,如果数据已经全部加载可以直接用GroupTableBuilder\r\n\r\n'g.ConnectionName = \"数据源\"\r\n'外部数据表的话要指定数据源名称\r\n                g.Groups.AddDef(\"产品\")\r\n                g.Totals.AddDef(\"数量\")\r\n                g.VerticalTotal\r\n= True\r\n                g.Filter\r\n=  \"Year(日期)\r\n= \"\r\n& y\r\n&\r\n\" And Month(日期)\r\n= \"\r\n& m\r\n'后台统计才可以使用这种表达式的哦\r\n                Dim\r\ndt As\r\nDataTable = g.Build(False)\r\n\r\n\r\n\r\n'统计数据,将结果保存为网页,然后发送这个网页\r\n                Dim\r\nsb As New\r\nStringBuilder\r\n                sb.AppendLine(y\r\n& \"年\"\r\n& m\r\n& \"月各产品销售数量: <br/><br/>\")\r\n                For\r\nEach dr As\r\nDataRow In\r\ndt.DataRows\r\n                    sb.AppendLine(dr(\"产品\")\r\n& \":\"\r\n& dr(\"数量\")\r\n& \"<br/>\")\r\n                Next\r\n                sb.AppendLine(\"<br/>\")\r\n                sb.AppendLine(\"统计时间:\"\r\n& Date.Now)\r\n'在网页显示统计时间,提醒用户这可能不是最新的结果\r\n                FileSys.WriteAllText(fl,\r\nsb.ToString,\r\nFalse, Encoding.UTF8)\r\n'记得用UTF8格式保存网页\r\n                e.WriteFile(fl)\r\n'发送保存的网页\r\n\r\nEnd\r\nIf\r\n        Else\r\n            Dim\r\nsb As\r\nNew StringBuilder\r\n\r\nsb.AppendLine(\"<form\r\nenctype='multipart/form-data' action='saletj.htm' method='post'\r\nid='form1'\r\nname='form1'>\")\r\n            sb.AppendLine(\"请输入要统计的年月:</br></br>\")\r\n            sb.AppendLine(\"<input\r\nname='tj' id='tj' value='按年月统计'\r\nhidden>\")\r\n'这个输入框不会显示，仅用于标记\r\n            sb.AppendLine(\"年:\r\n<input type='number' name='year' id='year' min='1999' max='2018'><br/><br/>\")\r\n            sb.AppendLine(\"月:\r\n<input type='number' name='month' id='month' min='1' max='12'><br/><br/>\")\r\n            sb.AppendLine(\"<input\r\nType='submit' name='Sumbit' id='Sumbit' value='开始统计'>\")\r\n            sb.AppendLine(\"</form>\")\r\n            e.WriteString(sb.ToString)\r\n        End\r\n If\r\nEnd\r\nSelect\r\n\r\n## 简化HttpRequest事件代码\r\n\r\n简化HttpRequest事件代码\r\n\r\n\r\n简化HttpRequest事件代码\r\n\r\n到上一节位置，我们的代码已经有点长了，这只是一个页面而已哦。\r\n随着页面的增加，代码会十倍甚至百倍成长，而Foxtable的代码编辑器并不适合编辑超长的代码，所以最好使用自定义函数，一个页面做成一个函数，方便管理。\r\n关于自定义函数，大家可以参考：[自定义函数](http://www.foxtable.com/webhelp/scr/1486.htm)\r\n\r\n用上一节的任务作为例子，我们采用自定义函数来实现：\r\n\r\n**1、定义函数**\r\n\r\n首先定义一个名为saletj的自定义函数，代码为：\r\n\r\nDim\r\ne As\r\nRequestEventArgs\r\n= args(0)\r\nIf\r\ne.PostValues.ContainsKey(\"tj\")\r\nAndAlso e.PostValues(\"tj\")\r\n= \"按年月统计\"\r\nThen\r\n\r\n\r\nDim y\r\nAs Integer\r\n= 0\r\n    Dim\r\nm As\r\nInteger = 0\r\n    Integer.TryParse(e.PostValues(\"year\"),\r\ny)\r\n'获取用户输入的年\r\n    Integer.TryParse(e.PostValues(\"month\"),\r\nm)\r\n'获取用户输入的月\r\n    If\r\ny = 0 OrElse\r\nm = 0 Then\r\n        e.WriteString(\"请输入正确的年月!\")\r\n    Else\r\n        Dim fl\r\nAs String =\r\n\"d:\\web\\temp\\\" &\r\ny &\r\nm &\r\n\".htm\"\r\n'合成文件名\r\n        If FileSys.FileExists(fl)\r\nThen\r\n'如果文件存在\r\n            Dim ifo\r\nAs new\r\nFileInfo(fl)\r\n\r\n'如果不是本月,或者文件创建时间在1个小时内,则发送文件后返回\r\n\r\nIf\r\ny <> Date.Today.Year\r\nOrElse m <>\r\nDate.Today.Month\r\nOrElse (Date.Now\r\n- Ifo.LastWriteTime).TotalHours\r\n< 1\r\n                e.WriteFile(fl)\r\n                Return\r\n\"\"\r\n            End\r\nIf\r\n        End\r\nIf\r\n        '根据输入的年月,统计各产品的销售数量\r\n        Dim g\r\nAs New\r\nSQLGroupTableBuilder(\"统计表1\",\r\n\"订单\")\r\n'这里使用后台统计,如果数据已经全部加载可以直接用GroupTableBuilder\r\n\r\n'g.ConnectionName = \"数据源\"\r\n'外部数据表的话要指定数据源名称\r\n        g.Groups.AddDef(\"产品\")\r\n        g.Totals.AddDef(\"数量\")\r\n        g.VerticalTotal\r\n= True\r\n        g.Filter\r\n= \"Year(日期)\r\n= \"\r\n& y\r\n&\r\n\" And Month(日期)\r\n= \"\r\n& m\r\n'后台统计才可以使用这种表达式的哦\r\n        Dim dt\r\nAs DataTable\r\n= g.Build(False)\r\n\r\n'统计数据,将结果保存为网页,然后发送这个网页\r\n        Dim sb\r\nAs New\r\nStringBuilder\r\n        sb.AppendLine(y\r\n& \"年\"\r\n& m\r\n& \"月各产品销售数量: <br/><br/>\")\r\n        For Each\r\ndr As\r\nDataRow In\r\ndt.DataRows\r\n            sb.\n\nArchive v2.0.3: 20 files, 216484 bytes\n\nFiles: references/00_关于文档构成.md (2632b), references/01_关于移动开发.md (1112b), references/02_关于功能演示.md (492b), references/03_关于网页设计.md (542b), references/04_HTML入门.md (120974b), references/05_WeUI框架.md (320984b), references/06_扩展WeUI的功能.md (339b), references/07_客户端类.md (27069b), references/08_JSON解析.md (26750b), references/09_微信接口.md (278113b), references/10_更多接口.md (2978b), references/11_生成并发送PDF文件.md (2186b), references/12_生成并发送Word文件.md (1481b), references/13_用Excel报表生成网页.md (27468b), references/14_类型参考.md (19626b), references/15_异步编程.md (37254b), references/16_Web数据源.md (8037b), skill-card.md (3409b), SKILL.md (3334b), _meta.json (136b)\n\nArchive v2.0.2: 20 files, 216322 bytes\n\nFiles: references/00_关于文档构成.md (2632b), references/01_关于移动开发.md (1112b), references/02_关于功能演示.md (492b), references/03_关于网页设计.md (542b), references/04_HTML入门.md (120974b), references/05_WeUI框架.md (320984b), references/06_扩展WeUI的功能.md (339b), references/07_客户端类.md (27069b), references/08_JSON解析.md (26750b), references/09_微信接口.md (278113b), references/10_更多接口.md (2978b), references/11_生成并发送PDF文件.md (2186b), references/12_生成并发送Word文件.md (1481b), references/13_用Excel报表生成网页.md (27468b), references/14_类型参考.md (19626b), references/15_异步编程.md (37254b), references/16_Web数据源.md (8037b), skill-card.md (2997b), SKILL.md (3334b), _meta.json (136b)\n\nArchive v2.0.1: 20 files, 216364 bytes\n\nFiles: references/00_关于文档构成.md (2632b), references/01_关于移动开发.md (1112b), references/02_关于功能演示.md (492b), references/03_关于网页设计.md (542b), references/04_HTML入门.md (120974b), references/05_WeUI框架.md (320984b), references/06_扩展WeUI的功能.md (339b), references/07_客户端类.md (27069b), references/08_JSON解析.md (26750b), references/09_微信接口.md (278113b), references/10_更多接口.md (2978b), references/11_生成并发送PDF文件.md (2186b), references/12_生成并发送Word文件.md (1481b), references/13_用Excel报表生成网页.md (27468b), references/14_类型参考.md (19626b), references/15_异步编程.md (37254b), references/16_Web数据源.md (8037b), skill-card.md (3211b), SKILL.md (3268b), _meta.json (136b)\n\nArchive v2.0.0: 20 files, 216409 bytes\n\nFiles: references/00_关于文档构成.md (2632b), references/01_关于移动开发.md (1112b), references/02_关于功能演示.md (492b), references/03_关于网页设计.md (542b), references/04_HTML入门.md (120974b), references/05_WeUI框架.md (320984b), references/06_扩展WeUI的功能.md (339b), references/07_客户端类.md (27069b), references/08_JSON解析.md (26750b), references/09_微信接口.md (278113b), references/10_更多接口.md (2978b), references/11_生成并发送PDF文件.md (2186b), references/12_生成并发送Word文件.md (1481b), references/13_用Excel报表生成网页.md (27468b), references/14_类型参考.md (19626b), references/15_异步编程.md (37254b), references/16_Web数据源.md (8037b), skill-card.md (3355b), SKILL.md (3268b), _meta.json (136b)\n\nArchive v1.1.0: 20 files, 214673 bytes\n\nFiles: references/00_关于文档构成.md (2606b), references/01_关于移动开发.md (1086b), references/02_关于功能演示.md (466b), references/03_关于网页设计.md (516b), references/04_HTML入门.md (120292b), references/05_WeUI框架.md (320321b), references/06_扩展WeUI的功能.md (310b), references/07_客户端类.md (26951b), references/08_JSON解析.md (26738b), references/09_微信接口.md (277432b), references/10_更多接口.md (2980b), references/11_生成并发送PDF文件.md (2154b), references/12_生成并发送Word文件.md (1448b), references/13_用Excel报表生成网页.md (27239b), references/14_类型参考.md (18630b), references/15_异步编程.md (36795b), references/16_Web数据源.md (8037b), skill-card.md (3136b), SKILL.md (2576b), _meta.json (136b)\n\nArchive v1.0.0: 20 files, 214951 bytes\n\nFiles: references/00_关于文档构成.md (2632b), references/01_关于移动开发.md (1112b), references/02_关于功能演示.md (492b), references/03_关于网页设计.md (542b), references/04_HTML入门.md (122015b), references/05_WeUI框架.md (320321b), references/06_扩展WeUI的功能.md (337b), references/07_客户端类.md (26951b), references/08_JSON解析.md (26738b), references/09_微信接口.md (277432b), references/10_更多接口.md (2980b), references/11_生成并发送PDF文件.md (2186b), references/12_生成并发送Word文件.md (1481b), references/13_用Excel报表生成网页.md (27239b), references/14_类型参考.md (18630b), references/15_异步编程.md (36795b), references/16_Web数据源.md (8037b), skill-card.md (2925b), SKILL.md (2727b), _meta.json (136b)","readmeExcerpt":"Skill: 狐表(Foxtable) 移动端开发 Owner: golgys0621 Summary: 狐表(Foxtable)移动端开发完整文档，涵盖HTML入门、WeUI框架、微信接口、客户端类、JSON解析、异步编程、Web数据源等，完整保留多层导航结构 Tags: HTML:2.0.5, WeUI:2.0.5, foxtable:2.0.5, latest:2.0.5, weui:2.0.3 Version history: v2.0.5 | 2026-07-21T08:17:21.204Z | user v2.0.5: 修正显示名称，统一为「狐表(Foxtable)」品牌前缀 v2.0.4 | 2026-07-21T08:09:31.907Z | user v2.0.4: 统一命名为「狐表(Foxtable)」品牌前缀 v2.0.3 | 2026-07-21T02:49:22.965Z | user 提供规范 ski","codeSnippets":[],"executableExamples":[],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\r\nname: \"狐表(Foxtable) 移动端开发\"\r\ndescription: \"狐表(Foxtable)移动端开发完整文档，涵盖HTML入门、WeUI框架、微信接口、客户端类、JSON解析、异步编程、Web数据源等，完整保留多层导航结构\"\r\nread_when:\r\n  - foxmobile\r\n  - foxtable 移动开发\r\n  - foxtable 手机\r\n  - foxtable 微信接口\r\n  - foxtable WeUI\r\n  - foxtable web开发\r\n  - foxtable HTML\r\n  - foxtable 客户端类\r\n  - foxtable JSON\r\n  - foxtable 异步编程\r\n  - foxtable web数据源\r\n  - 狐表 移动\r\n---\r\n\r\n# Foxtable 移动开发文档\r\n\r\n本技能包含狐表(Foxtable)移动端开发的完整帮助文档，从 CHM 文件转换而来，**完整保留了原始的多层导航结构（最深 5 层）**。Foxtable 移动开发用于基于 Foxtable 构建手机端管理系统，支持 B/S 架构的 Web 应用开发。\r\n\r\n## 何时使用\r\n\r\n当用户询问 Foxtable/狐表 的移动开发、Web开发、微信接口、WeUI框架、HTML页面开发、客户端类、JSON解析、异步编程、Web数据源等问题时，加载对应的参考文档进行回答。\r\n\r\n## 参考文档索引\r\n\r\n按章节组织，每个文件包含该章节下的全部页面内容（文件内用 `#`/`##`/`###`/`####`/`#####` 多层标题还原导航层级，可精确定位）：\r\n\r\n| 文件 | 章节 | 页面数 | 内容概要 |\r\n|------|------|--------|----------|\r\n| `references/00_关于文档构成.md` | 关于文档构成 | 1 | 文档结构说明 |\r\n| `references/01_关于移动开发.md` | 关于移动开发 | 1 | 移动开发概述 |\r\n| `references/02_关于功能演示.md` | 关于功能演示 | 1 | 功能演示说明 |\r\n| `references/03_关于网页设计.md` | 关于网页设计 | 1 | 网页设计原则 |\r\n| `references/04_HTML入门.md` | HTML入门 | 46 | HTML基础、服务器配置、Hello World |\r\n| `references/05_WeUI框架.md` | WeUI框架 | 109 | WeUI组件、样式、布局（核心章节） |\r\n| `references/06_扩展WeUI的功能.md` | 扩展WeUI | 1 | WeUI功能扩展 |\r\n| `references/07_客户端类.md` | 客户端类 | 14 | 客户端API与操作 |\r\n| `references/08_JSON解析.md` | JSON解析 | 9 | JSON数据处理 |\r\n| `references/09_微信接口.md` | 微信接口 | 76 | 微信公众号、支付、模板消息等（核心章节） |\r\n| `references/10_更多接口.md` | 更多接口 | 1 | 其他API接口 |\r\n| `references/11_生成并发送PDF文件.md` | PDF文件 | 1 | PDF生成与发送 |\r\n| `references/12_生成并发送Word文件.md` | Word文件 | 1 | Word生成与发送 |\r\n| `references/13_用Excel报表生成网页.md` | Excel报表网页 | 6 | Excel报表转网页 |\r\n| `references/14_类型参考.md` | 类型参考 | 10 | 数据类型速查 |\r\n| `references/15_异步编程.md` | 异步编程 | 14 | 异步操作与回调 |\r\n| `references/16_Web数据源.md` | Web数据源 | 3 | Web端数据源连接 |\r\n\r\n## 使用方式\r\n\r\n1. 根据用户问题判断属于哪个章节\r\n2. 读取对应的 references 文件\r\n3. 文件内按 `#`/`##`/`###` 等多层标题定位具体内容（导航层级已完整保留）\r\n4. WeUI框架(05)和微信接口(09)是两个核心章节\r\n\r\n## 文档来源\r\n\r\n原始文件: `E:\\foxtable\\Development\\foxmobile.chm` (6.6MB)\r\n转换时间: 2026-07-21\r\n原始页面数: 295 页 HTML\r\n导航层级: 最深 5 层，完整保留（无截断）"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn77z6synmrxqwaha37s8r2b7h8a9k07\",\n  \"slug\": \"foxmobile-dev-doc\",\n  \"version\": \"2.0.5\",\n  \"publishedAt\": 1784621841204\n}"},{"path":"references/00_关于文档构成.md","content":"# 关于文档构成\r\n\r\n\r\n# 关于文档构成\r\n\r\n关于文档构成\r\n\r\n\r\n**关于文档构成**\r\n\r\n本文档是Foxtable的《高级开发指南》，是Foxtable主文档的补充，用于介绍部分Foxtable高级开发版特有的功能，例如网页设计入门、手机管理系统的开发，微信接入、三层架构设计和多线程编程等等。\r\n\r\n\r\n我们必须在已经掌握《使用指南》和《开发指南》的基础上，才能学习《高级开发指南》。\r\n\r\n本文档主要由七个部分组成\r\n\r\n* **HTML入门**\r\n  这一章介绍了如何开启一个HTTP服务，如果和用HTML生成网页，如何接收用户通过网页输入的数据，如何保存用户上传的文件等等。一般用户对于HTML稍作了解即可，因为实际开发的时候，我们一般会用内置的WeUI框架生成网页。\r\n* **WeUI框架**\r\n  这一张是重点，通过这一章，你会发现在Foxtable生成网页是多么的简单。\r\n  WeUI框架来自于腾讯，我们对其进行了一些扩展，增加了表格和AJAX功能，通过框架，我们可以用非常简单的代码生成专业效果的网页，本框架主要针对移动设备，虽然也能在PC上使用，但在手机上的效果更好。\r\n* **客户端类**\r\n\r\n  这一章介绍了客户端类HttpClient，Foxtable通过HttpClient和HTTP服务进行交互，包括第三方的HTTP服务。我们在进行微信开发的时候，实际上就是通过HttpClient调用腾讯提供的HTTP服务。\r\n* **JSON解析**\r\n\r\n  这一章介绍了JSON和XML数据的解析生成，互联网中的数据传递格式主要是JSON和XML，例如进行微信开发的时候，你会发现除了回调部分的数据是XML格式，其余都是JSON格式。\r\n  通过这一章，你会发现在Foxtable进行JSON和XML的解析与生成，实在是太轻松了。\r\n* **微信接口**\r\n\r\n  这一章介绍了如何在Foxtable进行微信开发，分别针对公众号和企业号进行讲述，基本上涵盖了所有微信常用接口，由于我们进行了适度的封装，微信开发变得前所未有的轻松。实际上不仅是微信开发，你可以用Foxtable完成任何第三方接口的开发，帮助就提供了一个阿里云快递查询接口的开发示例，只有短短的三行代码而已\r\n  。\r\n* **Web数据源**\r\n  Foxtable默认是两层结构，客户端直接连接数据库，这一章介绍如何使用Web数据源设计三层架构的系统，使得客户端不再直连数据库。\r\n* **异步编程**\r\n  这一节章介绍如何如何用异步编程同时处理多个任务，以提高服务器的负载能力。"},{"path":"references/01_关于移动开发.md","content":"# 关于移动开发\r\n\r\n\r\n# 关于移动开发\r\n\r\n关于移动开发\r\n\r\n\r\n**关于移动开发**\r\n\r\nFoxtable的高级开发版提供了移动开发功能。\r\n\r\nFoxtable的移动开发功能以B/S形式实现，内置手机网页自动生成功能，你不需要额外的学习，你可以完全不懂网页设计，也可以快速开发基于手机的企业管理系统，包括服务端的架设和网页的生成，全都可以在Foxtable中用最简单的方式完成，不仅简单到人人都能掌握，而且效率极高，相对于其它开发工具，代码节省了85%左右。\r\n\r\n此外高级开发版还提供了微信接入功能，用户可以直接通过微信输入、查询、统计和审核数据，还可以直接通过手机扫描二维码并将扫描结果发送到Foxtable。\r\n\r\n下面是Foxtable自动生成的一些手机网页。\r\n\r\n一个订单编辑页面：\r\n\r\n\r\n\r\n图片浏览和上传页面：\r\n\r\n\r\n\r\n输入界面很灵活：\r\n\r\n\r\n\r\n可以直接在手机端生成Excel报表：\r\n\r\n\r\n\r\n可以直接扫描条形码或者二维码输入数据：\r\n\r\n\r\n\r\n\r\n提供了多种输入组件："},{"path":"references/02_关于功能演示.md","content":"# 关于功能演示\r\n\r\n\r\n# 关于功能演示\r\n\r\n关于功能演示\r\n\r\n\r\n**关于功能演示**\r\n\r\n你可以用微信扫描以下二维码，关注Foxtable的公众号：\r\n\r\n我们在这个公众号上做了一个简单的示例，进入公众号后，单击菜单“移动测试”可以进入这个示例。\r\n\r\n我们很难用一个例子演示完毕Foxtable的移动开发功能，只要你认真学习本文档，就能很轻松地用Foxtable设计出很专业的手机端管理系统。"}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":"狐表(Foxtable)移动端开发完整文档，涵盖HTML入门、WeUI框架、微信接口、客户端类、JSON解析、异步编程、Web数据源等，完整保留多层导航结构 Skill: 狐表(Foxtable) 移动端开发 Owner: golgys0621 Summary: 狐表(Foxtable)移动端开发完整文档，涵盖HTML入门、WeUI框架、微信接口、客户端类、JSON解析、异步编程、Web数据源等，完整保留多层导航结构 Tags: HTML:2.0.5, WeUI:2.0.5, foxtable:2.0.5, latest:2.0.5, weui:2.0.3 Version history: v2.0.5 | 2026-07-21T08:17:21.204Z | user v2.0.5: 修正显示名称，统一为「狐表(Foxtable)」品牌前缀 v2.0.4 | 2026-07-21T08:09:31.907Z | user v2.0.4: 统一命名为「狐表(Foxtable)」品牌前缀 v2.0.3 | 2026-07-21T02:49:22.965Z | user 提供规范 ski","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":585,"uniquenessScore":53,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-11T19:27:56.617Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-11T19:27:56.617Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-11T22:50:20.350Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}