其他
This commit is contained in:
Vendored
BIN
Binary file not shown.
@@ -0,0 +1,688 @@
|
||||
# Autocomplete (/ui/components/react/autocomplete)
|
||||
|
||||
|
||||
|
||||
|
||||
## Usage [#usage]
|
||||
|
||||
```tsx
|
||||
import {
|
||||
Autocomplete,
|
||||
AutocompleteInput,
|
||||
AutocompleteContent,
|
||||
AutocompleteEmpty,
|
||||
AutocompleteList,
|
||||
AutocompleteItem,
|
||||
} from '@appica/ui-react/autocomplete'
|
||||
```
|
||||
|
||||
```tsx
|
||||
const frameworks = ['Next.js', 'Remix', 'Astro', 'Nuxt.js', 'SvelteKit', 'SolidStart']
|
||||
|
||||
<Autocomplete items={frameworks}>
|
||||
<AutocompleteInput placeholder="Search a framework" />
|
||||
<AutocompleteContent>
|
||||
<AutocompleteEmpty>No frameworks found.</AutocompleteEmpty>
|
||||
<AutocompleteList>
|
||||
{(item) => (
|
||||
<AutocompleteItem key={item} value={item}>
|
||||
{item}
|
||||
</AutocompleteItem>
|
||||
)}
|
||||
</AutocompleteList>
|
||||
</AutocompleteContent>
|
||||
</Autocomplete>
|
||||
```
|
||||
|
||||
`Autocomplete` is a compound component. The root holds the data (`items`) and shared options (`size`, `variant`, `clearable`, `icon`); the parts compose the input and the filtered popup:
|
||||
|
||||
* **`AutocompleteInput`** - the text field, styled like [`Input`](/ui/components/react/input). Add a clear button with `clearable` and a dropdown chevron with `icon` (both on the root, both off by default).
|
||||
* **`AutocompleteContent`** - the portalled, auto-positioned popup. Accepts the shared [floating props](/ui/components/react/popover) (`side`, `align`, `sideOffset`, …).
|
||||
* **`AutocompleteList`** - the scrollable list. Pass a render function to map over the filtered items, or static `AutocompleteItem`s.
|
||||
* **`AutocompleteItem`** - one option; its `value` is what selection reports.
|
||||
* **`AutocompleteEmpty`** - shown only when the filter matches nothing.
|
||||
|
||||
### Reaching the positioner [#reaching-the-positioner]
|
||||
|
||||
`AutocompleteContent` is the popup, so its `className` styles the popup. The elements around it have their own escape hatches: `positionerProps` for the element that places it and `portalProps` for the portal. The positioning props you reach for most - `side`, `align`, `sideOffset`, `anchor`, the collision props - are already flat props on `AutocompleteContent`.
|
||||
|
||||
The common reason to use one is theming. A popup portals to `<body>`, so CSS inheritance stops at that boundary and a popup opened from inside a scoped dark region renders light. Putting the class on the positioner fixes it, because the class only has to land on some ancestor of the popup:
|
||||
|
||||
```tsx
|
||||
<AutocompleteContent positionerProps={{ className: 'dark' }}>…</AutocompleteContent>
|
||||
```
|
||||
|
||||
The flat `container` prop moves the portal target itself. It's for shadow DOM and iframes, not theming - see [Dark Mode](/ui/docs/react/dark-mode#scoped-dark-regions-and-portals) and [Composition](/ui/docs/react/composition#portals-and-the-container-prop).
|
||||
|
||||
## Examples [#examples]
|
||||
|
||||
### Variants [#variants]
|
||||
|
||||
`variant` switches the input appearance between a bordered `outline` (default) and a filled `soft` field.
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import {
|
||||
Autocomplete,
|
||||
AutocompleteInput,
|
||||
AutocompleteContent,
|
||||
AutocompleteEmpty,
|
||||
AutocompleteList,
|
||||
AutocompleteItem,
|
||||
} from '@appica/ui-react/autocomplete'
|
||||
|
||||
const frameworks = ['Next.js', 'Remix', 'Astro', 'Nuxt.js', 'SvelteKit', 'SolidStart', 'Gatsby', 'Vite']
|
||||
const variants = ['outline', 'soft'] as const
|
||||
|
||||
export default function AutocompleteVariants() {
|
||||
return (
|
||||
<div className="flex w-full max-w-70 flex-col gap-4">
|
||||
{variants.map((variant) => (
|
||||
<div key={variant}>
|
||||
<Autocomplete items={frameworks} variant={variant}>
|
||||
<AutocompleteInput placeholder="Search a framework" aria-label={`Search (${variant})`} />
|
||||
<AutocompleteContent>
|
||||
<AutocompleteEmpty>No frameworks found.</AutocompleteEmpty>
|
||||
<AutocompleteList>
|
||||
{(item: string) => (
|
||||
<AutocompleteItem key={item} value={item}>
|
||||
{item}
|
||||
</AutocompleteItem>
|
||||
)}
|
||||
</AutocompleteList>
|
||||
</AutocompleteContent>
|
||||
</Autocomplete>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Sizes [#sizes]
|
||||
|
||||
`size` on the root scales the input, popup radius, and items together.
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import {
|
||||
Autocomplete,
|
||||
AutocompleteInput,
|
||||
AutocompleteContent,
|
||||
AutocompleteEmpty,
|
||||
AutocompleteList,
|
||||
AutocompleteItem,
|
||||
} from '@appica/ui-react/autocomplete'
|
||||
|
||||
const frameworks = ['Next.js', 'Remix', 'Astro', 'Nuxt.js', 'SvelteKit', 'SolidStart', 'Gatsby', 'Vite']
|
||||
const sizes = ['sm', 'md', 'lg'] as const
|
||||
|
||||
export default function AutocompleteSizes() {
|
||||
return (
|
||||
<div className="flex w-full max-w-70 flex-col gap-4">
|
||||
{sizes.map((size) => (
|
||||
<div key={size}>
|
||||
<Autocomplete items={frameworks} size={size}>
|
||||
<AutocompleteInput placeholder="Search a framework" aria-label={`Search (${size})`} />
|
||||
<AutocompleteContent>
|
||||
<AutocompleteEmpty>No frameworks found.</AutocompleteEmpty>
|
||||
<AutocompleteList>
|
||||
{(item: string) => (
|
||||
<AutocompleteItem key={item} value={item}>
|
||||
{item}
|
||||
</AutocompleteItem>
|
||||
)}
|
||||
</AutocompleteList>
|
||||
</AutocompleteContent>
|
||||
</Autocomplete>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Clearable & chevron [#clearable--chevron]
|
||||
|
||||
Set `clearable` to add a clear (✕) button once there's a value, and `icon` to add a chevron that toggles the popup. Both render inside the input and are off by default.
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import {
|
||||
Autocomplete,
|
||||
AutocompleteInput,
|
||||
AutocompleteContent,
|
||||
AutocompleteEmpty,
|
||||
AutocompleteList,
|
||||
AutocompleteItem,
|
||||
} from '@appica/ui-react/autocomplete'
|
||||
|
||||
const frameworks = ['Next.js', 'Remix', 'Astro', 'Nuxt.js', 'SvelteKit', 'SolidStart', 'Gatsby', 'Vite']
|
||||
|
||||
export default function AutocompleteClearable() {
|
||||
return (
|
||||
<div className="w-full max-w-70">
|
||||
<Autocomplete items={frameworks} defaultValue="Next.js" clearable icon>
|
||||
<AutocompleteInput placeholder="Search a framework" aria-label="Search a framework" />
|
||||
<AutocompleteContent>
|
||||
<AutocompleteEmpty>No frameworks found.</AutocompleteEmpty>
|
||||
<AutocompleteList>
|
||||
{(item: string) => (
|
||||
<AutocompleteItem key={item} value={item}>
|
||||
{item}
|
||||
</AutocompleteItem>
|
||||
)}
|
||||
</AutocompleteList>
|
||||
</AutocompleteContent>
|
||||
</Autocomplete>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Items with icons [#items-with-icons]
|
||||
|
||||
Items can render any content. Mark a leading [icon](/ui/icons) with `data-icon="start"` so it gets the same inner spacing as in [`Button`](/ui/components/react/button). When `items` are objects rather than strings, give the root an `itemToStringValue` so the filter and input know how to read each one. Here the selected command's icon is mirrored back into the input via `AutocompleteInput`'s `startSlot`.
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import {
|
||||
Autocomplete,
|
||||
AutocompleteInput,
|
||||
AutocompleteContent,
|
||||
AutocompleteEmpty,
|
||||
AutocompleteList,
|
||||
AutocompleteItem,
|
||||
} from '@appica/ui-react/autocomplete'
|
||||
import { LayoutDashboard, User, Settings, CreditCard, Bell, Mail } from '@appica/icons-react'
|
||||
|
||||
interface Command {
|
||||
label: string
|
||||
icon: React.ElementType
|
||||
}
|
||||
|
||||
const commands: Command[] = [
|
||||
{ label: 'Dashboard', icon: LayoutDashboard },
|
||||
{ label: 'Profile', icon: User },
|
||||
{ label: 'Settings', icon: Settings },
|
||||
{ label: 'Billing', icon: CreditCard },
|
||||
{ label: 'Notifications', icon: Bell },
|
||||
{ label: 'Messages', icon: Mail },
|
||||
]
|
||||
|
||||
export default function AutocompleteWithIcons() {
|
||||
const [value, setValue] = React.useState('')
|
||||
const selected = commands.find((command) => command.label === value)
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-70">
|
||||
<Autocomplete
|
||||
items={commands}
|
||||
value={value}
|
||||
onValueChange={(v: string) => setValue(v)}
|
||||
itemToStringValue={(item) => (item as Command).label}
|
||||
clearable
|
||||
icon
|
||||
>
|
||||
<AutocompleteInput
|
||||
placeholder="Search commands"
|
||||
aria-label="Search commands"
|
||||
startSlot={selected ? <selected.icon aria-hidden /> : null}
|
||||
/>
|
||||
<AutocompleteContent>
|
||||
<AutocompleteEmpty>No commands found.</AutocompleteEmpty>
|
||||
<AutocompleteList>
|
||||
{(command: Command) => (
|
||||
<AutocompleteItem key={command.label} value={command}>
|
||||
<command.icon data-icon="start" />
|
||||
{command.label}
|
||||
</AutocompleteItem>
|
||||
)}
|
||||
</AutocompleteList>
|
||||
</AutocompleteContent>
|
||||
</Autocomplete>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Country search [#country-search]
|
||||
|
||||
The same object-item pattern with [country flags](/ui/country-flags) - a typical country picker. Each item renders a flag before the name, and the selected country's flag is mirrored into the input through `startSlot`.
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import {
|
||||
Autocomplete,
|
||||
AutocompleteInput,
|
||||
AutocompleteContent,
|
||||
AutocompleteEmpty,
|
||||
AutocompleteList,
|
||||
AutocompleteItem,
|
||||
} from '@appica/ui-react/autocomplete'
|
||||
import { CountryFlagRounded } from '@appica/country-flags-react'
|
||||
|
||||
interface Country {
|
||||
code: string
|
||||
name: string
|
||||
}
|
||||
|
||||
const countries: Country[] = [
|
||||
{ code: 'au', name: 'Australia' },
|
||||
{ code: 'br', name: 'Brazil' },
|
||||
{ code: 'ca', name: 'Canada' },
|
||||
{ code: 'fr', name: 'France' },
|
||||
{ code: 'de', name: 'Germany' },
|
||||
{ code: 'it', name: 'Italy' },
|
||||
{ code: 'jp', name: 'Japan' },
|
||||
{ code: 'es', name: 'Spain' },
|
||||
{ code: 'gb', name: 'United Kingdom' },
|
||||
{ code: 'us', name: 'United States' },
|
||||
]
|
||||
|
||||
export default function AutocompleteCountrySearch() {
|
||||
const [value, setValue] = React.useState('')
|
||||
const selected = countries.find((country) => country.name === value)
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-70">
|
||||
<Autocomplete
|
||||
items={countries}
|
||||
value={value}
|
||||
onValueChange={(v: string) => setValue(v)}
|
||||
itemToStringValue={(item) => (item as Country).name}
|
||||
clearable
|
||||
icon
|
||||
>
|
||||
<AutocompleteInput
|
||||
placeholder="Search a country"
|
||||
aria-label="Search a country"
|
||||
startSlot={selected ? <CountryFlagRounded code={selected.code} aria-hidden /> : null}
|
||||
/>
|
||||
<AutocompleteContent>
|
||||
<AutocompleteEmpty>No countries found.</AutocompleteEmpty>
|
||||
<AutocompleteList>
|
||||
{(country: Country) => (
|
||||
<AutocompleteItem key={country.code} value={country}>
|
||||
<CountryFlagRounded code={country.code} data-icon="start" aria-hidden />
|
||||
{country.name}
|
||||
</AutocompleteItem>
|
||||
)}
|
||||
</AutocompleteList>
|
||||
</AutocompleteContent>
|
||||
</Autocomplete>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Grouped options [#grouped-options]
|
||||
|
||||
Pass a grouped `items` array (`{ value, items }[]`) and render each group with `AutocompleteGroup` + `AutocompleteLabel` + `AutocompleteCollection`. The outer render function iterates groups; `AutocompleteCollection` renders the items inside each.
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import {
|
||||
Autocomplete,
|
||||
AutocompleteInput,
|
||||
AutocompleteContent,
|
||||
AutocompleteEmpty,
|
||||
AutocompleteList,
|
||||
AutocompleteItem,
|
||||
AutocompleteGroup,
|
||||
AutocompleteLabel,
|
||||
AutocompleteCollection,
|
||||
} from '@appica/ui-react/autocomplete'
|
||||
|
||||
interface ProduceGroup {
|
||||
value: string
|
||||
items: string[]
|
||||
}
|
||||
|
||||
const groups: ProduceGroup[] = [
|
||||
{ value: 'Fruits', items: ['Apple', 'Banana', 'Cherry', 'Grapefruit', 'Mango', 'Orange', 'Strawberry'] },
|
||||
{ value: 'Vegetables', items: ['Broccoli', 'Carrot', 'Cucumber', 'Lettuce', 'Potato', 'Spinach', 'Tomato'] },
|
||||
{ value: 'Herbs', items: ['Basil', 'Mint', 'Oregano', 'Parsley', 'Rosemary', 'Thyme'] },
|
||||
]
|
||||
|
||||
export default function AutocompleteGrouped() {
|
||||
return (
|
||||
<div className="w-full max-w-70">
|
||||
<Autocomplete items={groups}>
|
||||
<AutocompleteInput placeholder="Search produce" aria-label="Search produce" />
|
||||
<AutocompleteContent>
|
||||
<AutocompleteEmpty>No produce found.</AutocompleteEmpty>
|
||||
<AutocompleteList>
|
||||
{(group: ProduceGroup) => (
|
||||
<AutocompleteGroup key={group.value} items={group.items}>
|
||||
<AutocompleteLabel>{group.value}</AutocompleteLabel>
|
||||
<AutocompleteCollection>
|
||||
{(item: string) => (
|
||||
<AutocompleteItem key={item} value={item}>
|
||||
{item}
|
||||
</AutocompleteItem>
|
||||
)}
|
||||
</AutocompleteCollection>
|
||||
</AutocompleteGroup>
|
||||
)}
|
||||
</AutocompleteList>
|
||||
</AutocompleteContent>
|
||||
</Autocomplete>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Grid layout [#grid-layout]
|
||||
|
||||
Set `grid` on the root to lay options out in a grid (arrow keys move in two dimensions). `AutocompleteList`'s `cols` controls how many items per row.
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import {
|
||||
Autocomplete,
|
||||
AutocompleteInput,
|
||||
AutocompleteContent,
|
||||
AutocompleteEmpty,
|
||||
AutocompleteList,
|
||||
AutocompleteItem,
|
||||
} from '@appica/ui-react/autocomplete'
|
||||
|
||||
const tags = ['React', 'Vue', 'Svelte', 'Angular', 'Solid', 'Qwik', 'Preact', 'Lit']
|
||||
|
||||
export default function AutocompleteGrid() {
|
||||
return (
|
||||
<div className="w-full max-w-70">
|
||||
<Autocomplete items={tags} grid icon>
|
||||
<AutocompleteInput placeholder="Search a library" aria-label="Search a library" />
|
||||
<AutocompleteContent>
|
||||
<AutocompleteEmpty>No libraries found.</AutocompleteEmpty>
|
||||
<AutocompleteList cols={2}>
|
||||
{(item: string) => (
|
||||
<AutocompleteItem key={item} value={item} className="flex-1">
|
||||
{item}
|
||||
</AutocompleteItem>
|
||||
)}
|
||||
</AutocompleteList>
|
||||
</AutocompleteContent>
|
||||
</Autocomplete>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
### Controlled [#controlled]
|
||||
|
||||
Drive the input value yourself with `value` + `onValueChange` - useful for syncing with form state or reacting to every keystroke.
|
||||
|
||||
```tsx
|
||||
'use client'
|
||||
|
||||
import * as React from 'react'
|
||||
import {
|
||||
Autocomplete,
|
||||
AutocompleteInput,
|
||||
AutocompleteContent,
|
||||
AutocompleteEmpty,
|
||||
AutocompleteList,
|
||||
AutocompleteItem,
|
||||
} from '@appica/ui-react/autocomplete'
|
||||
|
||||
const frameworks = ['Next.js', 'Remix', 'Astro', 'Nuxt.js', 'SvelteKit', 'SolidStart', 'Gatsby', 'Vite']
|
||||
|
||||
export default function AutocompleteControlled() {
|
||||
const [value, setValue] = React.useState('')
|
||||
|
||||
return (
|
||||
<div className="flex w-full max-w-70 flex-col gap-3">
|
||||
<Autocomplete items={frameworks} value={value} onValueChange={(v: string) => setValue(v)} clearable>
|
||||
<AutocompleteInput placeholder="Search a framework" aria-label="Search a framework" />
|
||||
<AutocompleteContent>
|
||||
<AutocompleteEmpty>No frameworks found.</AutocompleteEmpty>
|
||||
<AutocompleteList>
|
||||
{(item: string) => (
|
||||
<AutocompleteItem key={item} value={item}>
|
||||
{item}
|
||||
</AutocompleteItem>
|
||||
)}
|
||||
</AutocompleteList>
|
||||
</AutocompleteContent>
|
||||
</Autocomplete>
|
||||
<p className="text-foreground-muted text-sm">
|
||||
Current value: <span className="text-foreground font-medium">{value || '-'}</span>
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
## RTL [#rtl]
|
||||
|
||||
Every Appica UI component supports right-to-left layouts out of the box. Set the `dir` attribute on a container (commonly your `<html>` element) so CSS logical properties resolve correctly, and wrap your tree in `DirectionProvider` so direction-aware behavior (roving focus, popup placement, and the like) follows the same direction.
|
||||
|
||||
```tsx
|
||||
import { DirectionProvider } from '@appica/ui-react/providers/direction-provider'
|
||||
|
||||
export default function RootLayout({ children }) {
|
||||
return (
|
||||
<html lang="ar" dir="rtl">
|
||||
<body>
|
||||
<DirectionProvider dir="rtl">{children}</DirectionProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
For setup details and caveats, see the [RTL guide](/ui/docs/react/rtl).
|
||||
|
||||
|
||||
## API reference [#api-reference]
|
||||
|
||||
`Autocomplete` wraps [Base UI's Autocomplete](https://base-ui.com/react/components/autocomplete). Each part forwards every remaining prop to the matching Base UI primitive, so anything it accepts works here - the tables below list the Appica additions and the most common props. See the Base UI docs for the complete API.
|
||||
|
||||
### Autocomplete [#autocomplete]
|
||||
|
||||
The root. Holds the data and the shared styling/behavior options; renders no DOM of its own.
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| ---------------------- | ---------------------------------------------------- | ----------- | ------------------------------------------------------------------------------------ |
|
||||
| `items` | `readonly T\[] \| {'{ value, items }[]'}` | - | The data to filter. A flat array, or `{ value, items }` objects for grouped options. |
|
||||
| `size` | `'sm' \| 'md' \| 'lg'` | `'md'` | Input height, popup radius, and item sizing. |
|
||||
| `variant` | `'outline' \| 'soft'` | `'outline'` | Input appearance - bordered or filled. |
|
||||
| `clearable` | `boolean` | `false` | Render a clear button inside the input when a value is present. |
|
||||
| `icon` | `boolean` | `false` | Render a chevron button that toggles the popup. |
|
||||
| `grid` | `boolean` | `false` | Lay options out as a navigable grid instead of a list. |
|
||||
| `value` | `string` | - | Controlled input value. Pair with `onValueChange`. |
|
||||
| `defaultValue` | `string` | - | Uncontrolled initial input value. |
|
||||
| `onValueChange` | `(value, details) => void` | - | Fires as the value changes (typing, selection, clear). |
|
||||
| `itemToStringValue` | `(item: T) => string` | - | Required for object items: how the filter and input read each one. |
|
||||
| `mode` | `'list' \| 'inline' \| 'both' \| 'none'` | `'list'` | Inline modes autofill the input with the highlighted item while navigating. |
|
||||
| `filter` | `function` | - | Match items against the query. Return `true` to keep an item. |
|
||||
| `children` | `ReactNode` | - | The content. |
|
||||
| `inline` | `boolean` | `false` | Render the list inline instead of in a popup. Pair with an unconditional `open`. |
|
||||
| `disabled` | `boolean` | `false` | Disable user interaction. |
|
||||
| `onOpenChange` | `function` | - | Fires when the popup opens or closes. |
|
||||
| `name` | `string` | - | Hidden input name for form submission. |
|
||||
| `modal` | `boolean` | `false` | Trap interaction inside the popup while it is open. |
|
||||
| `actionsRef` | `Ref` | - | Ref exposing imperative `unmount()` / `close()`. |
|
||||
| `open` | `boolean` | - | Controlled open state. Pair with `onOpenChange`. |
|
||||
| `defaultOpen` | `boolean` | `false` | Uncontrolled initial open state. |
|
||||
| `onOpenChangeComplete` | `function` | - | Fires once the open/close animation finishes. |
|
||||
| `autoHighlight` | `boolean` | `false` | Highlight the first match automatically as the user types. |
|
||||
| `keepHighlight` | `boolean` | `false` | Keep the highlight when the pointer leaves the list. |
|
||||
| `highlightItemOnHover` | `boolean` | `true` | Highlight items as the pointer moves over them. |
|
||||
| `openOnInputClick` | `boolean` | `false` | Open the popup when the input is clicked. |
|
||||
| `required` | `boolean` | `false` | Require a value before the form can submit. |
|
||||
| `readOnly` | `boolean` | `false` | Prevent choosing a different option. |
|
||||
| `loopFocus` | `boolean` | `true` | Wrap arrow-key focus from the end of the list back to the input. |
|
||||
| `onItemHighlighted` | `function` | - | Fires when an item is highlighted or unhighlighted. |
|
||||
| `inputRef` | `Ref` | - | Ref to the hidden input element. |
|
||||
| `filteredItems` | `array` | - | Pre-filtered items to render, bypassing the built-in filter. |
|
||||
| `virtualized` | `boolean` | `false` | Tell the list its items are virtualized externally. |
|
||||
| `limit` | `number` | `-1` | Maximum items to render. `-1` for no limit. |
|
||||
| `locale` | `string` | - | Locale used for string comparison. Defaults to the runtime locale. |
|
||||
| `submitOnItemClick` | `boolean` | `false` | Submit the owning form when an item is clicked. |
|
||||
|
||||
### AutocompleteInput [#autocompleteinput]
|
||||
|
||||
The text field. Renders Base UI's `Input` inside an `InputGroup`, styled like [`Input`](/ui/components/react/input).
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| ------------- | ------------------------------------------------------------- | ------- | --------------------------------------------------------------------- |
|
||||
| `startSlot` | `ReactNode` | - | Adornment rendered before the field, inside the input frame. |
|
||||
| `endSlot` | `ReactNode` | - | Adornment rendered after the field, inside the input frame. |
|
||||
| `placeholder` | `string` | `' '` | Placeholder text. |
|
||||
| `disabled` | `boolean` | `false` | Disables the field and the clear/chevron buttons. |
|
||||
| `render` | `ReactElement \| ((props, state) => ReactElement)` | - | Replace the underlying element, or compose it with another component. |
|
||||
| `className` | `string` | - | Extra classes on the input group, merged via `tailwind-merge`. |
|
||||
|
||||
Also forwards every native `<input>` prop (`onChange`, `name`, `aria-*`, `ref`, …).
|
||||
|
||||
### AutocompleteContent [#autocompletecontent]
|
||||
|
||||
The portalled, auto-positioned popup wrapping the list. Accepts the shared floating props.
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| ----------------------- | ------------------------------------------------------------------------------------- | ---------------------- | --------------------------------------------------------------------- |
|
||||
| `side` | `'top' \| 'right' \| 'bottom' \| 'left' \| 'inline-end' \| 'inline-start'` | `'bottom'` | Preferred side of the input to open on. |
|
||||
| `align` | `'start' \| 'center' \| 'end'` | - | Alignment along that side. |
|
||||
| `sideOffset` | `number` | `6` | Gap between the input and the popup. |
|
||||
| `alignOffset` | `number` | - | Shift along the alignment axis. |
|
||||
| `container` | `HTMLElement \| ref \| null` | - | Portal target - render the popup into a different node. |
|
||||
| `keepMounted` | `boolean` | `false` | Keep the popup mounted in the DOM while closed. |
|
||||
| `positionerProps` | `object` | - | Escape hatch for the positioner element (`style`, `render`, …). |
|
||||
| `portalProps` | `object` | - | Escape hatch for the portal element (`style`, `render`, …). |
|
||||
| `render` | `ReactElement \| ((props, state) => ReactElement)` | - | Replace the underlying element, or compose it with another component. |
|
||||
| `initialFocus` | `boolean \| ref \| function` | - | Where focus goes when the popup opens. |
|
||||
| `finalFocus` | `boolean \| ref \| function` | - | Where focus goes when the popup closes. |
|
||||
| `anchor` | `Element \| ref \| VirtualElement` | - | Element to position against, instead of the trigger. |
|
||||
| `positionMethod` | `'absolute' \| 'fixed'` | `'absolute'` | CSS `position` used for the popup. |
|
||||
| `collisionBoundary` | `Boundary` | `'clipping-ancestors'` | Element or rectangle the popup is confined to. |
|
||||
| `collisionPadding` | `number` | `5` | Extra space to keep from the boundary edge. |
|
||||
| `collisionAvoidance` | `CollisionAvoidance` | - | How the popup reacts when it would overflow the boundary. |
|
||||
| `sticky` | `boolean` | `false` | Keep the popup in view after the anchor scrolls away. |
|
||||
| `arrowPadding` | `number` | `5` | Minimum gap between the arrow and the popup's corners. |
|
||||
| `disableAnchorTracking` | `boolean` | `false` | Stop the popup following layout shifts of its anchor. |
|
||||
| `className` | `string` | - | Extra classes on the popup. |
|
||||
|
||||
### AutocompleteList [#autocompletelist]
|
||||
|
||||
The scrollable list container.
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| ----------- | ------------------------------------------------------------- | ------- | ------------------------------------------------------------------------- |
|
||||
| `cols` | `number` | - | Items per row in grid mode (defaults to 2 when `grid` is set). |
|
||||
| `className` | `string` | - | Extra classes, merged via `tailwind-merge`. |
|
||||
| `render` | `ReactElement \| ((props, state) => ReactElement)` | - | Replace the underlying element, or compose it with another component. |
|
||||
| `children` | `ReactNode \| (item, index) => ReactNode` | - | A render function over the filtered items, or static `AutocompleteItem`s. |
|
||||
|
||||
### AutocompleteItem [#autocompleteitem]
|
||||
|
||||
One option in the list.
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| -------------- | ------------------------------------------------------------- | ------- | ------------------------------------------------------------------------- |
|
||||
| `value` | `T` | - | The item this option represents; reported on selection. |
|
||||
| `children` | `ReactNode` | - | The content. |
|
||||
| `index` | `number` | - | The item's index in the list. Set it to skip automatic index calculation. |
|
||||
| `disabled` | `boolean` | `false` | Disable user interaction. |
|
||||
| `nativeButton` | `boolean` | `false` | Whether `render` produces a native `<button>`. Set `true` when it does. |
|
||||
| `render` | `ReactElement \| ((props, state) => ReactElement)` | - | Replace the underlying element, or compose it with another component. |
|
||||
| `className` | `string` | - | Extra classes (e.g. `flex-1` to fill a grid cell). |
|
||||
|
||||
### AutocompleteEmpty [#autocompleteempty]
|
||||
|
||||
Shown only when the filter matches nothing - otherwise it renders nothing.
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| ----------- | ------------------------------------------------------------- | ------- | --------------------------------------------------------------------- |
|
||||
| `render` | `ReactElement \| ((props, state) => ReactElement)` | - | Replace the underlying element, or compose it with another component. |
|
||||
| `children` | `ReactNode` | - | The empty-state message. |
|
||||
| `className` | `string` | - | Extra classes, merged via `tailwind-merge`. |
|
||||
|
||||
### AutocompleteStatus [#autocompletestatus]
|
||||
|
||||
A persistent row that stays mounted (e.g. a loading or count message), unlike `AutocompleteEmpty`.
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| ----------- | ------------------------------------------------------------- | ------- | --------------------------------------------------------------------- |
|
||||
| `render` | `ReactElement \| ((props, state) => ReactElement)` | - | Replace the underlying element, or compose it with another component. |
|
||||
| `children` | `ReactNode` | - | The status message. |
|
||||
| `className` | `string` | - | Extra classes, merged via `tailwind-merge`. |
|
||||
|
||||
### AutocompleteGroup [#autocompletegroup]
|
||||
|
||||
Wraps one labeled section of a grouped list.
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| ----------- | ------------------------------------------------------------- | ------- | ---------------------------------------------------------------------- |
|
||||
| `items` | `readonly T[]` | - | The group's items, so the inner `AutocompleteCollection` can map them. |
|
||||
| `render` | `ReactElement \| ((props, state) => ReactElement)` | - | Replace the underlying element, or compose it with another component. |
|
||||
| `className` | `string` | - | Extra classes, merged via `tailwind-merge`. |
|
||||
|
||||
### AutocompleteLabel [#autocompletelabel]
|
||||
|
||||
The visible label for an `AutocompleteGroup`.
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| ----------- | ------------------------------------------------------------- | ------- | --------------------------------------------------------------------- |
|
||||
| `render` | `ReactElement \| ((props, state) => ReactElement)` | - | Replace the underlying element, or compose it with another component. |
|
||||
| `children` | `ReactNode` | - | The label text. |
|
||||
| `className` | `string` | - | Extra classes, merged via `tailwind-merge`. |
|
||||
|
||||
### AutocompleteCollection [#autocompletecollection]
|
||||
|
||||
Renders the items inside an `AutocompleteGroup`.
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| ---------- | ------------------------------------------- | ------- | ------------------------------------------------------------------- |
|
||||
| `children` | `(item, index) => ReactNode` | - | Render function for each item - `(item) => <AutocompleteItem … />`. |
|
||||
|
||||
### AutocompleteSeparator [#autocompleteseparator]
|
||||
|
||||
A thin divider between sections.
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| ------------- | ------------------------------------------------------------- | -------------- | --------------------------------------------------------------------- |
|
||||
| `render` | `ReactElement \| ((props, state) => ReactElement)` | - | Replace the underlying element, or compose it with another component. |
|
||||
| `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Layout axis. |
|
||||
| `className` | `string` | - | Extra classes, merged via `tailwind-merge`. |
|
||||
|
||||
### AutocompleteTrigger [#autocompletetrigger]
|
||||
|
||||
A standalone button that opens the popup (e.g. a tag picker that isn't a text field).
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| -------------- | ------------------------------------------------------------- | ------- | ---------------------------------------------------------------------------- |
|
||||
| `disabled` | `boolean` | `false` | Disable user interaction. |
|
||||
| `nativeButton` | `boolean` | `true` | Whether `render` produces a native `<button>`. Set `false` when it does not. |
|
||||
| `render` | `ReactElement \| ((props, state) => ReactElement)` | - | Compose the trigger with another element. |
|
||||
| `className` | `string` | - | Extra classes, merged via `tailwind-merge`. |
|
||||
|
||||
Also forwards every native `<button>` prop (`onClick`, `aria-*`, `ref`, …).
|
||||
|
||||
### AutocompleteValue [#autocompletevalue]
|
||||
|
||||
Renders the current value for read-only display.
|
||||
|
||||
| Prop | Type | Default | Description |
|
||||
| ---------- | ---------------------------------------------- | ------- | -------------------------------------------------- |
|
||||
| `children` | `ReactNode \| (value) => ReactNode` | - | Static content, or a render function of the value. |
|
||||
|
||||
## Accessibility [#accessibility]
|
||||
|
||||
* The input has `role="combobox"`; the popup is a `listbox` (or `grid` when `grid` is set) and items are `option`/`gridcell`. Selection and active state are wired through `aria-activedescendant`.
|
||||
* **↑/↓** move the highlight, **Enter** selects, **Escape** closes the popup, and typing filters the list.
|
||||
* The clear and chevron buttons have `aria-label`s (`Clear selection`, `Toggle popup`) and become `disabled` when the input is disabled.
|
||||
* Always give `AutocompleteInput` a visible label or an `aria-label`.
|
||||
* Popup enter/exit transitions are wrapped so they're skipped when the user prefers reduced motion.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,209 @@
|
||||
# Installation (/ui/docs/react/installation)
|
||||
|
||||
|
||||
|
||||
Appica UI ships as a single tree-shakeable package. Components are built on
|
||||
[Base UI](https://base-ui.com) primitives and styled with Tailwind CSS v4 design
|
||||
tokens, so installation is two steps: add the package, then point Tailwind at it.
|
||||
|
||||
## Prerequisites [#prerequisites]
|
||||
|
||||
Appica UI targets modern React and Tailwind. Make sure your project has:
|
||||
|
||||
| Dependency | Version |
|
||||
| ------------ | -------- |
|
||||
| React | `>= 19` |
|
||||
| React DOM | `>= 19` |
|
||||
| Tailwind CSS | `>= 4.0` |
|
||||
|
||||
**React 19 is a hard requirement:** components use the modern ref-as-prop API and
|
||||
other React 19 patterns, with no `forwardRef` shims. If you're on React 18,
|
||||
upgrade before installing.
|
||||
|
||||
> **Warning - Tailwind v4 must be working first:**
|
||||
>
|
||||
> Appica UI doesn't bundle Tailwind - it relies on *your* project's Tailwind to compile the component styles. Before
|
||||
> installing, set up Tailwind v4 in your app: install `tailwindcss` **and** its build plugin (`@tailwindcss/vite` for
|
||||
> Vite, `@tailwindcss/postcss` for PostCSS/Next.js), then confirm that `@import 'tailwindcss';` in your global
|
||||
> stylesheet generates utilities. If Tailwind isn't compiling your own classes yet, Appica UI's components will render
|
||||
> unstyled.
|
||||
|
||||
## Install [#install]
|
||||
|
||||
```bash
|
||||
pnpm add @appica/ui-react # or: npm install @appica/ui-react / yarn add @appica/ui-react / bun add @appica/ui-react
|
||||
```
|
||||
|
||||
## Configure Tailwind [#configure-tailwind]
|
||||
|
||||
Import the design tokens after Tailwind in your global stylesheet, and add a
|
||||
`@source` directive so Tailwind scans the compiled library for class names.
|
||||
|
||||
```css title="globals.css"
|
||||
@import 'tailwindcss';
|
||||
@import '@appica/ui-react/styles.css';
|
||||
|
||||
@source '../node_modules/@appica/ui-react/dist';
|
||||
```
|
||||
|
||||
> **Warning - Don't skip @source - and give it a real path:**
|
||||
>
|
||||
> Tailwind ignores `node_modules` by default. Without the `@source` directive it won't generate the utility
|
||||
> classes used inside the compiled components, and they'll render unstyled.
|
||||
>
|
||||
> `@source` takes a path or glob **relative to the stylesheet that contains it**, not a bare package name -
|
||||
> `@source '@appica/ui-react'` does not resolve and silently scans nothing. Count the `../` needed to reach
|
||||
> `node_modules` from your CSS file: a stylesheet one level deep (`app/globals.css`, `src/index.css`) needs a
|
||||
> single `../`; a deeper one (`src/styles/app.css`) needs `../../`. This matches Tailwind's own
|
||||
> [`@source` documentation](https://tailwindcss.com/docs/functions-and-directives#source-directive), whose
|
||||
> example is `@source '../node_modules/@my-company/ui-lib';`.
|
||||
|
||||
The `styles.css` import brings in the full token system - colors, radii, shadows,
|
||||
typography, and the `light`/`dark` themes. See [Theming](/ui/docs/react/theming) to
|
||||
customize it.
|
||||
|
||||
### Why source-scanning is the default [#why-source-scanning-is-the-default]
|
||||
|
||||
Appica UI ships its components' class names as source for *your* Tailwind to
|
||||
compile, rather than defaulting to a prebuilt stylesheet you drop in. There's an
|
||||
opt-in prebuilt file for projects that don't run Tailwind (see [Without
|
||||
Tailwind](#without-tailwind) below), but wherever Tailwind is present the
|
||||
`@source` path is the one to reach for. That extra line buys a lot:
|
||||
|
||||
* **One deduplicated build.** The utilities the components use are generated into
|
||||
the same pass as your own, so shared classes (`flex`, `rounded-lg`, `px-4`…)
|
||||
are emitted once instead of shipping twice.
|
||||
* **Only what you use.** Tailwind tree-shakes across your app and the library
|
||||
together, folding the component utilities into your existing stylesheet instead
|
||||
of a second file, and unused component modules drop out with your bundler.
|
||||
* **Your tokens win.** Because the classes are compiled against *your* Tailwind
|
||||
config, overrides to the design tokens in [Theming](/ui/docs/react/theming) - colors,
|
||||
radii, spacing, fonts - flow into the components automatically. The prebuilt
|
||||
file freezes the non-token utilities (spacing, layout) at publish time.
|
||||
* **No version-locked CSS.** There's no compiled stylesheet to drift out of sync
|
||||
with your Tailwind version; the components restyle themselves against whatever
|
||||
Tailwind v4 you're on.
|
||||
|
||||
## Without Tailwind [#without-tailwind]
|
||||
|
||||
Appica UI is built on Tailwind, and the [source-scanning](#configure-tailwind)
|
||||
setup above is the recommended path. If your project doesn't use Tailwind, skip
|
||||
that step and import the **prebuilt stylesheet** instead. It's a single
|
||||
self-contained file with every component's styles and the full token system
|
||||
compiled in, so there's nothing to configure:
|
||||
|
||||
```css title="globals.css"
|
||||
@import '@appica/ui-react/css';
|
||||
```
|
||||
|
||||
No bundler? Link it straight from a CDN:
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="https://unpkg.com/@appica/ui-react/appica.css" />
|
||||
```
|
||||
|
||||
Tailwind stays an *optional* peer dependency, so nothing installs it on your
|
||||
behalf when you take this path.
|
||||
|
||||
> **Tip - Your tokens still override:**
|
||||
>
|
||||
> The prebuilt file keeps every design token as a CSS variable, so theming works exactly like
|
||||
> [Theming](/ui/docs/react/theming) describes: redefine the variable after the import and every component
|
||||
> follows, dark mode included.
|
||||
>
|
||||
> ```css
|
||||
> :root {
|
||||
> --primary: oklch(60% 0.25 150);
|
||||
> }
|
||||
> ```
|
||||
|
||||
What you trade for the convenience, versus the Tailwind path:
|
||||
|
||||
* **It carries a baseline reset.** The file includes Tailwind's Preflight, which
|
||||
normalizes element styles (margins, headings, borders) across your whole page.
|
||||
That's what makes it self-contained, but it is global.
|
||||
* **Only the token variables stay live.** Colors, fonts, radii, and shadows
|
||||
remain overridable through their CSS variables; the structural utilities
|
||||
(spacing, layout) are frozen at publish time, since there's no Tailwind config
|
||||
of yours for them to compile against.
|
||||
|
||||
> **Warning - Don't load both:**
|
||||
>
|
||||
> If you already run Tailwind, use [Configure Tailwind](#configure-tailwind) instead of this file.
|
||||
> Importing the prebuilt stylesheet *and* scanning with `@source` ships the reset and utilities twice.
|
||||
|
||||
## Add the provider [#add-the-provider]
|
||||
|
||||
Wrap your app in `ThemeProvider` to enable theming and dark mode. It injects a
|
||||
small inline script that applies the stored theme before first paint, so there's
|
||||
no flash of the wrong theme. In Next.js that's the root layout's `<body>`; in a
|
||||
Vite/CRA app, wrap your root the same way.
|
||||
|
||||
```tsx title="app/layout.tsx (Next.js)"
|
||||
import { ThemeProvider } from '@appica/ui-react/providers/theme-provider'
|
||||
|
||||
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<html lang="en" suppressHydrationWarning>
|
||||
<body>
|
||||
<ThemeProvider>{children}</ThemeProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
That's all you need to start. See [Theme Provider](/ui/docs/react/theme-provider) for
|
||||
the full API - custom themes, forcing a theme, and flash-prevention details - and
|
||||
the optional [`DirectionProvider`](/ui/docs/react/direction-provider) (RTL) and
|
||||
[`ReducedMotionProvider`](/ui/docs/react/reduced-motion-provider), which you add only
|
||||
when you need them.
|
||||
|
||||
## Framework notes [#framework-notes]
|
||||
|
||||
Appica UI is a plain package, so the steps above are the same everywhere - unlike a
|
||||
file-scaffolding CLI, there's no per-framework setup to learn. The only things that
|
||||
move are which Tailwind build plugin you install, where your global stylesheet lives,
|
||||
and where the provider goes:
|
||||
|
||||
| Framework | Tailwind plugin | Global stylesheet | Wrap the provider |
|
||||
| ----------------------------------------------------------------------------------------------------------- | ---------------------- | ----------------------- | ---------------------------- |
|
||||
| Next.js (App Router) | `@tailwindcss/postcss` | `app/globals.css` | root layout `<body>` |
|
||||
| Vite / CRA | `@tailwindcss/vite` | `src/index.css` | root component in `main.tsx` |
|
||||
| TanStack Start | `@tailwindcss/vite` | `src/styles/app.css` | root route component |
|
||||
| React Router 7 / Remix | `@tailwindcss/vite` | `app/app.css` | root component in `root.tsx` |
|
||||
| Astro | `@tailwindcss/vite` | `src/styles/global.css` | see note below |
|
||||
|
||||
Everything else (the `@import`s and the subpath imports) is identical regardless
|
||||
of framework. The one value that tracks the stylesheet is the `@source` path: count
|
||||
the `../` needed to reach `node_modules` from wherever your global CSS lives (see
|
||||
[Configure Tailwind](#configure-tailwind) above).
|
||||
|
||||
> **Warning - Astro needs the React integration and an island:**
|
||||
>
|
||||
> Astro renders React as [islands](https://docs.astro.build/en/concepts/islands/), not a
|
||||
> single tree, so it needs a little extra wiring. Add the `@astrojs/react` integration first,
|
||||
> then mount anything interactive with a client directive (e.g. `client:load`) - components
|
||||
> without one ship zero JS and won't react to theme changes. Because each island is its own
|
||||
> React root, a `ThemeProvider` placed in an `.astro` layout **won't** share context across
|
||||
> islands; either render an interactive section as one island wrapped in the provider, or apply
|
||||
> the theme class in your `.astro` `<head>` and use the provider only inside hydrated islands.
|
||||
|
||||
## Use a component [#use-a-component]
|
||||
|
||||
Import components from their subpath for the smallest bundle, and you're ready:
|
||||
|
||||
```tsx
|
||||
import { Button } from '@appica/ui-react/button'
|
||||
|
||||
export default function App() {
|
||||
return <Button>Get started</Button>
|
||||
}
|
||||
```
|
||||
|
||||
## Next steps [#next-steps]
|
||||
|
||||
* [Usage](/ui/docs/react/usage) - import conventions, variants, and composition basics.
|
||||
* [Theming](/ui/docs/react/theming) - customize colors, radii, and tokens.
|
||||
* [Dark Mode](/ui/docs/react/dark-mode) - build a theme toggle.
|
||||
* [Components](/ui/components/react/button) - browse the full catalog.
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 848 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 470 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 552 KiB |
+45
-2
@@ -2,7 +2,7 @@
|
||||
|
||||
> 所属:课程体系0.1 / 约束
|
||||
> 用途:**web 端(`website/`,Vite + React)C 端任何改动,必须同步检查并修复小程序端(`miniprogram/`,Taro + React)**。本文档是强制约束,不是建议。
|
||||
> 覆盖范围:**C 端(用户端)**:首页 / 报名 / OPC测评 / 政策测评 / 启动流程 / 登录认证。
|
||||
> 覆盖范围:**C 端(用户端)**:首页 / 报名 / OPC测评 / 政策测评 / 启动流程 / 调研 / 登录认证 / 我的(微信登录·手机绑定·我的报名·签到)。
|
||||
> 不覆盖:平台端(`/pine` 后台)与汇报端(`/pine/report`)——仅 web 存在,小程序不需要。
|
||||
|
||||
---
|
||||
@@ -27,7 +27,8 @@
|
||||
| OPC 测评题库 + 计分 + 评语 | **后端** `website/server/opcTest.js`(题目/计分/评语唯一权威) | web(待迁)、小程序通过 `GET /api/tests/opc/questions` + `POST /api/tests/opc/calculate` 取题目与完整报告 | 30/60 题、6 维计分、类型码、赛道/角色/工具映射、16 人设评语、报告 |
|
||||
| 政策测评题库 + 匹配 | `website/src/data/policyTest.js` | `miniprogram/src/utils/policyTest.js` | 5 题、computePolicy(政策/补贴/贷款匹配) |
|
||||
| 启动流程步骤库 + 生成 | `website/src/data/startPlan.js` | `miniprogram/src/utils/startPlan.js` | 地区/状态/园区/社保 → buildPlan 步骤 |
|
||||
| 报名字段契约 | `website/src/data/booking.js` | `miniprogram/src/utils/booking.js` | phone/code/name/status/want/topics/question/source |
|
||||
| OPC 创业调研题库 | `website/src/data/opcSurvey.js` | `miniprogram/src/utils/opcSurvey.js` | 37 题(D1–D5、D7、D8:画像/行业/理解/难题/AI赋能/政策诉求/创业启动与服务支持),single/multi/likert/text |
|
||||
| 报名字段契约 | `website/src/data/booking.js` | `miniprogram/src/utils/booking.js` | phone/code/name/status/topics/question/source + **eventId/eventTitle/eventStart**(按场次报名) |
|
||||
| 政策数据(知识库) | `知识库/02_云南OPC政策.md` | 小程序引用同一数据源 | 政策/补贴/贷款「以官方最新为准」 |
|
||||
|
||||
> ★ 架构变更登记(2026-08):**OPC 测评逻辑移至后端**。`server/opcTest.js` 为唯一权威(题目/计分/评语),小程序已改为后端驱动并删除本地 `opcTest.js`;**web 端 `src/data/opcTest.js` 尚未迁移,待办**——迁移后删除 web 本地副本,两端统一走后端。
|
||||
@@ -47,7 +48,22 @@
|
||||
| `GET /api/tests/opc/questions?version=quick\|full` | 下发测评题目 + 章节/轴元数据 | → `{ok, version, questions, sections, axisNames, dimLabels, disclaimer, quickNote}` |
|
||||
| `POST /api/tests/opc/calculate` | 提交答案,后端计分 + 返回完整报告/评语 | `{answers, version}` → `{ok, result}`(result 含 typeCode/persona/adaptIndex/adaptLevel/6维/4轴/赛道/角色/工具/下一步) |
|
||||
| `POST /api/policy-logs` / `POST /api/plan-logs` | 上报政策/流程 | `{username, answers/...}` → `{id}` |
|
||||
| `POST /api/survey-logs` | 提交 OPC 创业调研(公开、匿名可提交) | `{username, source, answers}` → `{id}` |
|
||||
| `GET /api/survey-logs` | 查看调研记录(管理端,需登录) | → `{list}` |
|
||||
| `POST /api/auth/wx-login` | 微信一键登录(演示用 code 哈希作伪 openid) | `{code, nickName?, avatarUrl?}` → `{token, username, name, avatar, phoneBound}` |
|
||||
| `GET /api/auth/me` | 当前账号资料 | auth → `{user:{username,name,avatar,phone,phoneBound}}` |
|
||||
| `POST /api/auth/bind-phone` | 绑定手机(短信验证码) | auth `{phone, code}` → `{ok, phoneBound}` |
|
||||
| `GET /api/bookings/mine` | 我的报名列表(匹配微信账号+绑定手机) | auth → `{list}` |
|
||||
| `GET /api/bookings/by-event/:eventId` | 查我是否报名该活动 + 审核状态(详情页) | auth → `{booked, auditStatus, checkinAt?}` |
|
||||
| `POST /api/checkins` | 活动签到(同一报名只签一次) | auth `{bookingId}` → `{ok, entry}` |
|
||||
| `POST /api/bookings` | 报名(支持登录态带 token 或 未登录手机号+验证码) | 登录态`{name,eventId,...}`;未登录`{phone,code,...}` → `{id, auditStatus}` |
|
||||
| `GET /api/events?current=1` | 当期排期 | → `{next, free, salon}` |
|
||||
| `GET /api/events?bookable=1` | 可报名场次(仅 open;排除 full/pending/invite/done) | → `{list}` |
|
||||
| `GET /api/events/:id` | 活动详情(详情页) | → `{event}`(含 mode/host/image/capacity/status) |
|
||||
|
||||
> **活动报名状态(5 态)**:`open` 报名中 / `full` 已满员 / `pending` 待开放 / `invite` 邀请中(定向,不接受公众报名) / `done` 已结束。
|
||||
> **报名审核状态(audit_status)**:`pending` 审核中 / `approved` 已通过 / `rejected` 未通过;`open` 活动报名直接通过,`full` 活动报名进入审核。
|
||||
> **登录态感知**:详情页已登录且绑定手机 → 免输手机号/验证码(用账号身份);已登录未绑手机 → 需先绑手机;未登录 → 完整手机号+验证码。
|
||||
|
||||
> **登录方式**:小程序端可加 `wx.login` 微信登录,但**用户端核心认证仍是手机号 + 验证码**(与 web 一致),微信登录仅作快捷入口。
|
||||
|
||||
@@ -71,6 +87,10 @@
|
||||
| 平台端 `/pine` 后台 | ✅ | ❌ 不做 | ✅ 已明确 |
|
||||
| 汇报端 `/pine/report` | ✅ | ❌ 不做 | ✅ 已明确 |
|
||||
| 微信登录/分享/订阅消息 | ❌ | ✅ 小程序特有 | 小程序增量 |
|
||||
| 调研页「云南 OPC 政策速览」手风琴 | ✅ | ❌ 仅结尾一行政策提示 | 新增(2026-08,调研页) |
|
||||
| 「我的」页(微信登录·手机绑定·我的报名·签到) | ❌ 暂不做 | ✅ 小程序特有 | 小程序增量 |
|
||||
| 底部 tab 布局 | 全站导航 | 活动/测评/首页/政策/我的(移除「流程」tab;「报名」改「活动」列表页) | 2026-08 调整 |
|
||||
| 活动详情页 | web 有独立路由 | 小程序非 tab 页(navigateTo?id=) | 两端均有,结构一致 |
|
||||
|
||||
> 新增任何差异项,**必须在本表登记**,避免小程序端被遗漏。
|
||||
|
||||
@@ -101,3 +121,26 @@
|
||||
---
|
||||
|
||||
> **一句话**:web 的 C 端改动 = 小程序的改动。改 web 前先看本文档清单,改完双端 + 后端一起验证,不允许只改 web。
|
||||
|
||||
## 八、数据/生成逻辑由后端管理(2026-08 后移)
|
||||
|
||||
> 测评/政策/流程/调研的**题目数据与生成逻辑全部后移至后端**,前端只负责提交答案 + 渲染结果。题目由后端下发,改动题目只需改 `server/app/*data·engine*.py`,无需改前端。
|
||||
|
||||
| 模块 | 题目下发 | 结果生成 | 前端 |
|
||||
|------|---------|---------|------|
|
||||
| OPC 测评 | `GET /api/tests/opc/questions` | `POST /api/tests/opc/calculate` | 测/答题 |
|
||||
| 政策测评 | `GET /api/policy/questions` | `POST /api/policy/calculate` | 答题 + 渲染 |
|
||||
| 启动流程 | `GET /api/plan/config` | `POST /api/plan/generate` | 选填 + 渲染 |
|
||||
| 调研 | `GET /api/survey/questions` | `POST /api/survey/submit` | 答题 + 提交 |
|
||||
|
||||
- 后端数据文件:`server/app/policy_data.py`、`policy_engine.py`、`survey_data.py`、`opc_data.py`、`opc_engine.py`。
|
||||
- 前端已删除本地数据副本(`data/policyTest|startPlan|opcSurvey`、`utils/policyTest|startPlan|opcSurvey`),统一走后端。
|
||||
|
||||
## 九、报名审核 & 图像上传
|
||||
|
||||
- **活动报名状态(5 态)**:`open`报名中 / `full`已满员 / `pending`待开放 / `invite`邀请中(定向不公开) / `done`已结束。
|
||||
- **报名审核(audit_status)**:`pending`审核中 / `approved`已通过 / `rejected`未通过。
|
||||
- 管理端:`PATCH /api/bookings/:id` `{auditStatus}`;`GET /api/bookings?audit=approved`。
|
||||
- pine 后台 `PineBookings` 已加"通过/拒绝"审核按钮 + 审核状态列。
|
||||
- **图像上传**:`POST /api/upload`(multipart,需登录)→ `{url}`;静态访问 `/uploads/{file}`。
|
||||
- 活动表单 `PineEvents` 支持上传封面(替代手填 URL)。
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
# 云南 OPC 创业伙伴深度调研方案
|
||||
|
||||
> 调研背景:**书记下周到园区视察**,借此契机组织一批在云南**从事或准备从事 OPC(AI 一人公司)创业**的伙伴进行深入调研,探讨云南 OPC 的行业分布、政策诉求、创业难题、认知水平。
|
||||
> 版本:2026‑08‑22 | 调研窗口:书记视察当周 | 编制依据:`搜集资料/政策汇总.MD`、`培训计划/课程体系0.1`(六维测评/六大赛道/自我认知工具)
|
||||
|
||||
---
|
||||
|
||||
## 〇、调研总目标(一句话)
|
||||
|
||||
**搞清楚"云南到底有多少人真的在/想用 AI 一人创业、他们做什么、卡在哪、最想要政府给什么"** —— 为三件事服务:①园区 OPC 生态招商与运营;②向书记汇报的政策建议清单;③培训课程/测评/陪跑产品迭代。
|
||||
|
||||
### 三个可交付的"开口"(都能对着书记讲)
|
||||
1. **一份《云南 OPC 创业者现状画像与政策诉求报告》**(数据版 + 精简汇报版)
|
||||
2. **一份《园区可落地的 OPC 扶持政策/服务建议清单》**(政府能接、园区能做的)
|
||||
3. **一份可沉淀的《云南 OPC 伙伴名录》**(为园区 OPC 社区、课程学员库蓄水)
|
||||
|
||||
---
|
||||
|
||||
## 一、调研对象与抽样
|
||||
|
||||
### 1.1 四类目标人群(分层抽样)
|
||||
| 类型 | 定义 | 获取渠道 | 优先级 |
|
||||
|------|------|---------|--------|
|
||||
| **A 已在做 OPC** | 有实际营收(哪怕几百元/月) | 园区入驻者、课程老学员、平台活跃创作者 | ★★★ 深度访谈优先 |
|
||||
| **B 准备做 OPC** | 正在转型/副业/筹备注册 | 培训咨询者、公益课报名者 | ★★ |
|
||||
| **C 观望意向** | 有想法但未行动 | 小红书/直播/社群粉丝 | ★ |
|
||||
| **D 相关服务方** | 孵化器/机构/人社/媒体 | 合作渠道 | ★ 少量补充视角 |
|
||||
|
||||
### 1.2 样本量建议
|
||||
- **线上问卷**:目标 **80–120 份**(A+B 为主,C 观察)
|
||||
- **深度访谈**:**10–15 场**(线下 1v1,30–45 分钟/场,A 类为主)
|
||||
- **焦点小组**:**1–2 组**(每组 6–8 人,2 小时,混合类型)
|
||||
- **现场快测**:书记视察现场设"OPC 创业微调研"扫码板,目标额外 **30–50 份**
|
||||
|
||||
### 1.3 招募渠道(一周内能启动的)
|
||||
- 园区自有社群、课程学员群、过往公益课/沙龙报名名单
|
||||
- `小红书` + 直播:发起"云南 AI 一人公司创业者,你在做什么"话题,评论区/私信捞人
|
||||
- 对接区/市人社局、创业孵化器、高校就业创业中心名单
|
||||
- 政策咨询热线 12333 相关就业服务机构的引荐
|
||||
|
||||
### 1.4 伦理与隐私(红线)
|
||||
- 问卷开头明示:**匿名、仅用于园区生态建设与政策研究、不向第三方透露个人信息**。
|
||||
- 深度访谈前签署《知情同意 + 录音许可》简短说明;引用观点一律脱敏(仅标注行业/阶段)。
|
||||
- 敏感信息(收入、家庭)设"可跳过"或区间选项,不强制。
|
||||
|
||||
---
|
||||
|
||||
## 二、调研方法组合(三层递进)
|
||||
|
||||
| 层次 | 方法 | 体量 | 目标 |
|
||||
|------|------|------|------|
|
||||
| **L1 广覆盖** | 结构化线上问卷 | 80–120 | 量化画像、行业分布、诉求排序 |
|
||||
| **L2 深理解** | 半结构化 1v1 访谈 | 10–15 | 摸清"为什么":动机、卡点细节、真实故事 |
|
||||
| **L3 共识碰撞** | 焦点小组/workshop | 1–2 组 | 对政策诉求与园区服务做排序与共创 |
|
||||
| **L4 现场快采** | 视察现场扫码微调研 | 30–50 | 借氛围低成本扩样本 + 向书记展示真实需求 |
|
||||
|
||||
> **关键原则**:问卷用来"确认规模与分布",访谈用来"挖掘因果与细节"。不要指望问卷回答"为什么"。
|
||||
|
||||
---
|
||||
|
||||
## 三、调研维度框架(6 + 1)
|
||||
|
||||
| 维度 | 要回答的问题 |
|
||||
|------|------------|
|
||||
| **D1 个人与创业画像** | 谁在做 OPC?什么身份、多大投入、多久了、全职还是副业? |
|
||||
| **D2 做的行业/赛道** | 云南 OPC 集中在哪些行业?对应我们六大赛道的分布? |
|
||||
| **D3 对 OPC 的理解** | 大家怎么理解"OPC/一人公司"?和公司法的"一人有限公司"是否混淆? |
|
||||
| **D4 遇到的难题/卡点** | 最卡的是资源/技能/方向/流量/财务/心态哪一环? |
|
||||
| **D5 政策与支持诉求** | 最希望政府给什么?对现有政策(云岭创业贷/补贴/社区)知晓与使用程度? |
|
||||
| **D6 附加·云南 OPC 政策** | 政策知晓度、信息来源、对政策落地痛点的看法(用于政策建议) |
|
||||
| **D7 附加·AI 工具与赋能真实度** | 用哪些 AI 工具、对各类 AI 技术了解多少、**是否真的用 AI 赋能了某个行业并产生真实交付/收入** |
|
||||
|
||||
---
|
||||
|
||||
## 四、线上问卷详细题目(共 6 部分 · 约 47 题)
|
||||
|
||||
> 题型:单选(S)、多选(M)、量表(Likert 5 分:1=完全不同意…5=完全同意)、开放(O)。加 `※` 为必答题。
|
||||
> 量表题措辞遵循"避免引导性",不出现"您是否觉得政策很好"这类诱导。
|
||||
|
||||
### 第一部分 · 个人与创业画像(D1)|8 题
|
||||
1. `S※` 您目前的创业状态是?
|
||||
□ 已注册并实际经营 OPC |□ 已开始做但未注册 |□ 正在筹备中 |□ 仅有意向、未行动 |□ 暂未考虑
|
||||
2. `S` 您的年龄段:□18–25 |□26–35 |□36–45 |□46–55 |□55 以上
|
||||
3. `S` 您的所在地:□昆明 |□玉溪 |□大理/丽江 |□西双版纳 |□红河 |□曲靖 |□其他【】
|
||||
4. `S` 您目前投入状态:□全职 OPC |□主业+副业 |□兼职/碎片时间 |□观望
|
||||
5. `S` 您从事 OPC 多久了?□<3 个月 |□3–12 个月 |□1–3 年 |□>3 年 |□还没开始
|
||||
6. `S` 您上一段/当前的主业背景是?□自媒体/内容 |□电商/销售 |□设计/文创 |□技术/IT |□文旅/餐饮 |□咨询/教培 |□财务/法务 |□传统就业在职 |□学生 |□其他【】
|
||||
7. `S` 您目前月均收入中,来自 OPC/副业的占比约为?
|
||||
□几乎为零 |□<30% |□30–60% |□60–90% |□90% 以上
|
||||
8. `M` 您现在投入了哪些"一人公司"相关能力?(可多选)
|
||||
□用 AI 做内容 |□搭过智能体/自动化流程 |□跑通过收款/成交 |□做过产品/服务交付 |□做过财税合规 |□运营过私域/社群 |□都还没系统做过
|
||||
|
||||
### 第二部分 · 做的行业/赛道(D2)|5 题
|
||||
9. `M※` 您目前在做的 / 计划做的 OPC 方向是?(对应云南六大赛道)
|
||||
□文旅体验(民宿/定制游/在地体验/非遗)|□内容 IP(自媒体/知识博主/个人 IP)|□数字咨询(顾问/1v1/知识产品)|□特色电商(普洱/咖啡/鲜花/农特产)|□跨境边贸(面向东南亚/南亚)|□本地服务(本地商家代运营/私域/企微)|□其他【】
|
||||
10. `S` 您最看好的云南 OPC 赛道是?(单选,理由可在 11 题补充)
|
||||
11. `O` 选这条赛道,最大的理由是?(一句话)
|
||||
12. `S` 您主要面向的客户是?□本地(云南)为主 |□全国线上 |□跨境/海外 |□本地+全国混合
|
||||
13. `S` 您认为云南做 OPC 相比外省的**最大优势**是?
|
||||
□文旅流量红利 |□边贸区位(25 边境县)|□特色农产品/物产 |□生活成本与创业环境 |□本地政策扶持 |□说不清
|
||||
|
||||
### 第三部分 · 对 OPC 的理解(D3)|6 题
|
||||
14. `M※` 提到"OPC/一人公司",您的第一反应是?(可多选)
|
||||
□个人靠 AI 独立创业 |□一个人开的公司(法务上的一人有限公司)|□自由职业/副业 |□数字游民 |□做自媒体 |□AI 提效工具使用者 |□不确定
|
||||
15. `S` 您认为"OPC"与"个人开个工作室/个体户"是一回事吗?
|
||||
□是,没区别 |□不完全,更偏 AI 单人全链路 |□完全不是一回事 |□说不清
|
||||
16. `L` "一个人 + AI 工具,就能完成从获客到交付的完整商业闭环"——您多大程度上认同?
|
||||
17. `L` "没有 AI 杠杆,一个人很难称得上真正的一人公司"——您多大程度认同?
|
||||
18. `L` "OPC 应该先跑通最小商业闭环,再谈注册和扶持"——您多大程度认同?
|
||||
19. `O` 用一句话向朋友解释"什么是 OPC"。
|
||||
|
||||
### 第四部分 · 遇到的难题/卡点(D4)|8 题
|
||||
20. `M※` 您现在最卡的是?(对应卡点五型,最多选 3 项)
|
||||
□资源型(没钱/没时间/没产品)|□技能型(不会做、不会用 AI 工具)|□方向型(不知道做什么/什么都想要)|□流量型(不会获客/没客户)|□产品交付型(不会做产品/交付不稳定)|□财务合规型(不会报税/怕税务风险)|□心态型(怕失败/没信心/内耗)|□其他【】
|
||||
21. `S` 其中**最致命的一个**卡点是?(单选)
|
||||
22. `L` "我能用 AI 工具提效,但不知道怎么把它变成稳定收入。"——您多大程度认同?
|
||||
23. `L` "我的内容/产品有人看,但一直难以成交。"——您多大程度认同?
|
||||
24. `O` 具体描述一个您最近遇到的、最想解决的实际困难(一两句话)。
|
||||
25. `M` 遇到困难时您更倾向求助谁?(可多选)
|
||||
□AI 工具/智能体 |□线上课程 |□同行社群 |□付费咨询/陪跑 |□政府/创业载体 |□自己硬扛
|
||||
26. `L` "如果有一套可落地的诊断 + 陪跑 + OPC 体系设计服务,能帮我明显少走弯路。"——您多大程度认同?
|
||||
27. `S` 您现在最需要的**一项**支持是?
|
||||
□AI 技能实操 |□商业模式梳理 |□获客/流量方法 |□财税合规 |□场地/工位 |□资金 |□政策申报指导 |□社群/伙伴
|
||||
|
||||
### 第四部分·B · AI 工具与赋能真实度(D7)|9 题
|
||||
|
||||
> 目的:检验"用 AI"是否落到真实商业赋能,而不仅是"会聊天"。
|
||||
|
||||
28. `M※` 您日常实际在用的 AI 工具有哪些?(可多选)
|
||||
□大模型对话(ChatGPT/豆包/文心/Kimi 等)|□AI 生图/设计(Midjourney/即梦/Canva 等)|□AI 生成视频/剪辑(Sora/剪映/可灵等)|□AI 写作/文案|□AI 编程(Cursor/Coze/低代码)|□AI 智能体/工作流搭建|□AI 数据分析/Excel |□AI 数字人/语音|□AI 电商/运营工具|□还没稳定用上任何 AI 工具
|
||||
29. `L` "我能熟练用 AI 完成我的日常产出(内容/交付/客服等)。"——您多大程度认同?
|
||||
30. `S` 您对**各类 AI 技术**的了解程度整体属于?
|
||||
□完全不了解 |□知道名词、没用过 |□会用常见工具 |□会用并懂原理 |□能搭建工作流/智能体
|
||||
31. `M` 以下 AI 能力,您**哪几项已经真实落地使用**过?(可多选)
|
||||
□AI 帮我生成内容/文案 |□AI 帮我做设计/图片 |□AI 帮我做视频/剪辑 |□AI 帮我写代码/搭网站 |□AI 帮我做客服/答疑 |□AI 帮我做市场/数据分析 |□AI 帮我搭自动化流程/智能体 |□AI 帮我做账/财税 |□都还没真实落地
|
||||
32. `S※` 您**是否真实地完成过"用 AI 赋能某个行业"**并产生了实际交付(客户用了、产品或服务上线了、收到钱了)?
|
||||
□有,且持续产生收入 |□有,做成了单次交付 |□在尝试中、尚未成功 |□只做过演示/自用,没对外交付 |□还没开始
|
||||
33. `O` 若"真实赋能过某个行业",请一句话说明是**哪个行业、用 AI 解决了它什么问题**(若没有可跳过)。
|
||||
34. `L` "我最大的障碍是不知道 AI 能怎么用在我的具体生意上。"——您多大程度认同?
|
||||
35. `L` "如果园区能提供 AI 工具实操 + 行业落地案例,对我帮助很大。"——您多大程度认同?
|
||||
36. `S` 您现在最需要的**一项** AI 支持是?
|
||||
□AI 工具手把手教学 |□行业 AI 落地案例 |□智能体搭建陪跑 |□AI 算力/工具补贴 |□AI 应用场景对接(真实业务)|□AI 合规与伦理指导
|
||||
|
||||
### 第五部分 · 政策与支持诉求(D5+D6)|10 题
|
||||
37. `S※` 您对云南 OPC 扶持政策的**了解程度**是?
|
||||
□完全不了解 |□听说过但不清楚 |□知道一些 |□比较了解 |□很了解并申报过
|
||||
38. `M` 您通过哪些渠道了解到的?(可多选)□政府官网 |□媒体/新闻 |□短视频/自媒体 |□朋友圈/社群 |□创业载体/孵化器 |□人社/培训机构 |□身边人 |□不清楚
|
||||
39. `S※` 您**是否申报/使用过**以下云南 OPC 相关扶持?(多选,每项"用过/没听过/听过没用过")
|
||||
- 云岭创业贷
|
||||
- 贷免扶补(创业担保贴息贷款)
|
||||
- 一次性创业补贴(5000 元)
|
||||
- 社保补贴 / 就业见习补贴
|
||||
- OPC 创业社区免费工位 / 孵化载体入驻
|
||||
- "创赢未来"2026 人工智能+OPC 专项赛 / 春城创业荟 OPC 组
|
||||
- 人才/职称相关(兴滇英才计划、AI 职称评审)
|
||||
40. `O` 若申报过或想申报,您觉得**申请流程最麻烦**的地方是?(开放,如材料、门槛、时效、信息不通)
|
||||
41. `L` "政策信息太分散,我不知道去哪查、能不能申报。"——您多大程度认同?
|
||||
42. `M※` 您**最希望政府提供**的支持是?(最多选 3 项)
|
||||
□真金白银补贴/减负 |□低成本融资/贷款担保 |□免费办公场地/工位 |□订单/业务场景对接 |□算力/AI 工具补贴 |□技能培训/创业辅导 |□财税法务合规支持 |□政策信息一站式入口 |□对外宣传/平台曝光 |□创业失败兜底/再就业保障
|
||||
43. `S` 如果只能选一项,您最需要的是?【承接 42】
|
||||
44. `L` "如果园区能提供 OPC 一站式服务(工位+培训+政策申报+订单对接),我愿意入驻。"——您多大程度认同?
|
||||
45. `O` 请给园区/政府提一个具体可落地的建议(不限篇幅,一句话也好)。
|
||||
46. `S※` 若园区后续组织 OPC 创业活动(沙龙/培训/社群/陪跑),您是否愿意参加?
|
||||
□非常愿意 |□愿意 |□看情况 |□不愿意 |□不愿留联系方式(可选)
|
||||
|
||||
**问卷尾部(可选,用于建立名录)**
|
||||
47. 若愿意后续被联系,请留:称呼 / 微信或手机 / 所在行业【】— *可跳过,用于《伙伴名录》*
|
||||
|
||||
---
|
||||
|
||||
## 五、深度访谈提纲(半结构化 · 10–15 场 · 30–45 分钟)
|
||||
|
||||
> 开场:说明用途(园区生态 + 政策研究 + 匿名脱敏),获取录音许可。
|
||||
> 访谈主线:**背景 → 怎么理解 → 做什么 → 卡在哪 → 要什么**。提问用"您能具体讲讲吗 / 能举个例吗"做探针,不预设答案。
|
||||
|
||||
### 1. 破冰与背景(3 分钟)
|
||||
- 请简单介绍下您自己,以及目前在做的 OPC 相关事情。
|
||||
|
||||
### 2. 对 OPC 的理解(5 分钟)
|
||||
- 您理解的"OPC / 一人公司"是什么?跟"个体户 / 一个人开公司"有什么不同?
|
||||
- 您当初是怎么接触到 OPC 这个词的?当时第一感觉是什么?
|
||||
- (探针)您觉得"一个人公司"最重要的是什么能力?AI 在其中扮演什么角色?
|
||||
|
||||
### 3. 行业与模式(8 分钟)
|
||||
- 您现在做的是哪个方向?为什么会选这个行业?(追问:是主动选还是被动的机会)
|
||||
- 您的客户是谁、怎么获客?营收和利润大概在什么量级(可区间)?
|
||||
- 云南的环境对您这个方向是助力还是阻力?具体体现在哪?
|
||||
|
||||
### 4. 创业难题与卡点(10 分钟)
|
||||
- 走到今天,您遇到的最大的三次困难是什么?(逐次追问当时怎么解决/卡了多久/损失多大)
|
||||
- 现在最卡住您的是哪一环:方向、技能、流量、成交、交付,还是财税/心态?
|
||||
- 您试过哪些办法?为什么没能解决?(探针:缺钱/缺方法/缺人/缺资源/缺信心)
|
||||
|
||||
### 4.5 AI 工具与赋能真实度(8 分钟)
|
||||
- 您现在实际用哪些 AI 工具?用得最多的是哪个,用来做什么?(追问具体场景,不是泛泛而谈)
|
||||
- 您对哪些 AI 技术比较懂?比如大模型、生图、视频、智能体/工作流、AI 编程——您到哪个程度?
|
||||
- 您有没有**真实地**用 AI 帮某个行业/某个客户解决过问题、做成过交付?(追问:是哪个行业、解决了什么问题、对方有没有为此付费或实际使用)——如果没有,卡在哪一步?
|
||||
- (探针)您觉得 AI 是帮您"提效",还是已经变成您生意里不可替代的一环了?
|
||||
- 您最想要哪种 AI 支持:工具教学、行业案例、智能体陪跑、算力补贴、还是真实业务场景对接?
|
||||
|
||||
### 5. 政策认知与诉求(10 分钟)
|
||||
- 您对云南现在的 OPC 扶持政策了解多少?(试探云岭创业贷、一次性补贴、创业社区)
|
||||
- 有没有申报/用过的?感受如何?
|
||||
- 如果政府要做一件事帮到您,**最希望是哪一件**?为什么?
|
||||
- (探针)给钱、给场地、给订单、给培训、给算力、给合规支持——哪个对您当前阶段最有用?
|
||||
|
||||
### 6. 对园区与课程的期待(5 分钟)
|
||||
- 如果您在本地有一个"OPC 创业社区/园区",您最希望里面有什么?
|
||||
- 您愿不愿意参加培训/陪跑/沙龙?什么形式您最愿意来?(线上/线下/公益/付费)
|
||||
- 您愿意认识其他云南 OPC 伙伴、互相合作吗?
|
||||
|
||||
### 7. 收尾
|
||||
- 除了这些,还有什么想告诉我们的?(留一个开放式出口)
|
||||
- 方便留下联系方式,后续 OPC 社群/活动第一时间通知您吗?(可拒绝)
|
||||
|
||||
---
|
||||
|
||||
## 六、焦点小组 / Workshop 设计(1–2 组 · 6–8 人 · 2 小时)
|
||||
|
||||
> 目的:把问卷和访谈里零散的诉求,通过群体讨论**排序和共创**出"政策建议清单 + 园区服务清单"。
|
||||
|
||||
- **环节 1(30min)**:各自 90 秒自我介绍 + 一句话说"我最想要的政府支持"。
|
||||
- **环节 2(40min)**:卡片法——每人写 3 张"政府该做的事"卡片,分类贴墙(钱/场地/订单/培训/算力/合规/信息),按热度排序,选出 TOP3 集体讨论。
|
||||
- **环节 3(30min)**:共创"理想 OPC 社区的样子"——每人贡献一个要素,拼出一张社区蓝图。
|
||||
- **环节 4(20min)**:投票"园区最该先做的一件事",并现场招募愿意加入 OPC 伙伴社群的人。
|
||||
|
||||
---
|
||||
|
||||
## 七、与书记视察现场的衔接(现场快采)
|
||||
|
||||
- **扫码微调研**:在视察路线节点摆"OPC 创业微调研"二维码桌,配 8 题精简版问卷(40 秒填完,含 1 题"是否真实用 AI 交付过某个行业"),同步发一张《云南 OPC 政策一页速览》折页。
|
||||
- **现场展示点**:把"已访谈伙伴的行业分布词云 + 政策诉求 TOP 榜"做成一块展板,作为向书记汇报的**一手证据**,直观展示"云南有人在做 OPC,他们在做什么、缺什么"。
|
||||
- **一对一引荐**:提前锁定 2–3 位有故事的 A 类 OPC 伙伴,安排在视察环节与书记/园区负责人自然交流,形成"真人真事"汇报素材。
|
||||
|
||||
> ⚠️ 提前与书记行程负责人确认:现场扫码不干扰视察节奏;展示点与交流位需提前踩点;参与伙伴需提前培训话术(不抢话、讲事实、给数据)。
|
||||
|
||||
---
|
||||
|
||||
## 八、实施计划(一周推进表)
|
||||
|
||||
| 天 | 事项 |
|
||||
|----|------|
|
||||
| D1 | 定稿问卷与访谈提纲;上线问卷;发出招募信息(园区群/学员群/小红书/直播/人社渠道) |
|
||||
| D2–D3 | 群发问卷 + 私信跟进;排期 10–15 场深度访谈;组建 1 个焦点小组 |
|
||||
| D4–D5 | 密集完成深度访谈 + 焦点小组;同步回收问卷(目标 80–120) |
|
||||
| D6 | 数据清洗与初步分析;生成"行业分布词云 + 政策诉求 TOP 榜"展板素材 |
|
||||
| D7(视察日) | 现场扫码快采 + 展板展示 + 2–3 位伙伴交流;回收现场问卷 |
|
||||
| D8–D10 | 汇总所有数据,产出《现状画像与政策诉求报告》+《园区 OPC 服务建议清单》+《伙伴名录》 |
|
||||
|
||||
---
|
||||
|
||||
## 九、数据分析方法
|
||||
|
||||
- **问卷量化**:频率分布 / 交叉表(行业 × 卡点、状态 × 政策诉求);量表题算均值与同意率。
|
||||
- **开放题与访谈**:编码归类(主题分析),把高频关键词做成词云(如"政策""流量""算力""场地""不会获客")。
|
||||
- **政策诉求排序**:合并问卷 33/34 与访谈/焦点小组结果,输出"政府支持优先级排序"。
|
||||
- **画像聚类**:按"创业状态 × 行业"给 A/B/C 三类分别画像,避免一刀切。
|
||||
- **对比**:若收集到一定量,对比"政策知晓度" vs "申报使用率"的落差(知晓但不用 → 说明落地有堵点)。
|
||||
|
||||
---
|
||||
|
||||
## 十、输出物清单
|
||||
|
||||
1. **《云南 OPC 创业者现状画像与政策诉求报告》**(正式版 + 书记精简汇报版一页纸)
|
||||
2. **《园区可落地的 OPC 扶持政策与服务建议清单》**(分"政府能接/园区能做"两类,逐条带证据)
|
||||
3. **《云南 OPC 伙伴名录》**(含联系方式,授权者才收录,脱敏归档)
|
||||
4. **调研过程资产**:问卷回收数据、访谈录音/纪要(脱敏)、词云/展板源文件
|
||||
|
||||
---
|
||||
|
||||
## 附录 A · 云南 OPC 政策一页速览(给受访者/折页用)
|
||||
|
||||
> 来源:`搜集资料/政策汇总.MD`。**全部以官方原文为准,自媒体高额奖励说法无官方文件支撑。**
|
||||
|
||||
| 政策 | 要点 |
|
||||
|------|------|
|
||||
| 《关于支持服务人工智能OPC创业的若干措施》 | 省人社厅 2026‑07 印发;OPC 创业社区/免费工位、云岭创业贷、创业补贴、兴滇英才、AI 职称、一网通办、创业大赛设 OPC 赛道 |
|
||||
| **云岭创业贷** | 试点昆明、普洱;个人信用贷最高 50 万、抵押担保最高 1000 万;企业信用贷最高 500 万、抵押担保最高 3000 万;政府性融资担保增信;需银行风控审批 |
|
||||
| **一次性创业补贴** | 5000 元,OPC 经营主体同等享受;另有社保补贴、就业见习补贴 |
|
||||
| **贷免扶补(创业担保贷款)** | 10 类重点群体个人最高 20 万、合伙最高 110 万、3 年期财政贴息、可免反担保 |
|
||||
| **创业赛事 OPC 赛道** | "创赢未来"2026 人工智能+OPC 专项赛(省级,已收官);春城创业荟 OPC 荟创赛组(昆明,报名截止 2026‑09‑10,需落地注册) |
|
||||
| **地方 OPC 社区** | 玉溪红塔区(全省首家 OPC 标杆社区,模型券/免费工位/电商专项补贴);昆明西山庾园社区 AI 青创空间(免费工位/算力/注册代办) |
|
||||
| **上位纲领** | 《"创业云南"建设三年行动计划(2024‑2026)》—— 以上补贴贷款的顶层依据 |
|
||||
|
||||
**名词**:OPC(One Person Company,AI 一人公司)= 个人依托 AI 独立完成商业全链路的轻量化创业;**不等于**公司法"一人有限公司";个体户、个独、一人有限公司只要符合 AI 单人模式均可申报。
|
||||
|
||||
**给受访者的提示**:政策落地细节各地不同,申报前拨 **12333** 咨询当地公共就业服务机构。
|
||||
|
||||
---
|
||||
|
||||
## 附录 B · 风险与注意事项
|
||||
|
||||
1. **不夸大、不承诺**:问卷/访谈/折页不得出现"补贴 200 万""人人可贷"等无官方来源说法,避免受访者误解,也避免给园区带来政策风险。
|
||||
2. **信息敏感性**:收入、联系方式等敏感字段均设可选项;名录只收录明确授权者。
|
||||
3. **避免引导性**:量表题不带倾向预设("政策很好"之类);开放题先让受访者自由回答,再追问。
|
||||
4. **样本偏差提醒**:主要通过园区/课程/人社渠道招募,样本可能偏向"已接触 OPC 服务"人群,报告需注明代表性局限。
|
||||
5. **书记视察红线**:现场活动以"不干扰、不表演"为原则,素材真实,不提前"导演"数据。
|
||||
Reference in New Issue
Block a user