# yii2-ledap

## 安装

推荐使用 [composer](http://getcomposer.org/download/)来安装

输入:

```bash
composer require --prefer-dist ethercap/yii2-ledap "dev-master"
```

或在 `composer.json` 文件中加入如下内容:

```
"ethercap/yii2-ledap": "dev-master"
```

## 使用说明

### 简介

本代码主要用到如下框架：

* [Bootstrap4](https://getbootstrap.com/docs/4.3/getting-started/introduction/)
* [Vue 2.6](https://cn.vuejs.org/v2/guide/instance.html)
* [Vue-Bootstrap 2.0.4](https://bootstrap-vue.js.org/docs)
* [Ledap](https://github.com/ethercap/ledap)
* [yii2-api-base](https://github.com/ethercap/yii2-api-base)

### 起步

复制layout。在复制后，你可以编辑layout的内容。

```bash
cp vendor/ethercap/yii2-ledap/src/gii/layout.php xxx/views/layouts/main.php
```

### 代码自动生成

代码生成器与gii/crud的使用方法一致：

```bash
php yii gii/ledapCrud --controllerClass="\frontend\controllers\TestController" --modelClass="common\models\Test" --searchModelClass="\frontend\forms\TestSearch"
```

在运行后，你能够在浏览器中访问这些页面。生成的代码如下：

1. **xxController :** 处理http请求的yii2 Controller.
2. **views/xx/\*.php :** 模板文件，用来渲染html.
3. **views/xx/\*.api :** Api接口文件，用来渲染接口.
4. **xx/web/xx/\*.js :** 模板文件中对应的js文件，它会被在view中渲染出，并带有一个hash串。

生成的文件结构如下所示：

![代码文件列表](/files/-LloBhiYAwYE-nKKFmOE)

### 全局组件

#### 1. loading

```markup
<!-- 在.js中注册变量isLoading-->
<div class="page-loading-container" v-if="isLoading">
  <div class="page-loading">加载中…</div>
</div>
```

#### 2. toast

toast是对[vue-bootstrap的toast](https://bootstrap-vue.js.org/docs/components/toast)进行了封装，做了一些默认的配置及支持了html.

```javascript
// 弹出默认的toast
this.$toast("hello world");
// toast支持html,vm代表当前的vue component
this.$toast('<span class="text-danger" @click="vm.alert()">hello world</span>')
// toast配置,具体参数详见 https://bootstrap-vue.js.org/docs/components/toast
this.$toast("hello world", {
    variant: 'error/danger/info...',
    title: 'BootstrapVue Toast',
    autoHideDelay: 5000,    
});
//toast 与下面基本等价,下面不支持直接写html
 this.$bvToast.toast(message, options);
```

#### 3. alert

alert是对[vue-bootstrap的modal](https://bootstrap-vue.js.org/docs/components/modal)进行了封装，做了一些默认的配置及支持。

```javascript
//弹出alert
this.$alert("hello world");
//弹出alert,支持html，vm代表当前的vue component
this.$alert('<span class="text-danger" @click="vm.alert()">hello world</span>');
this.$alert("hello world", {
      variant: 'error/danger/info...',
      title: 'Confirmation',
      size: 'sm',
      buttonSize: 'sm',
      okVariant: 'success',
      headerClass: 'p-2 border-bottom-0',
      footerClass: 'p-2 border-top-0',
      centered: true
}).then(()=>{
    console.log("ok");
}).catch(()=>{
    console.log("error");
});
//alert与下面的调用基本等价
this.$bvModal.msgBoxOk(message, options);
```

#### 4. confirm

confirm是对[vue-bootstrap的modal](https://bootstrap-vue.js.org/docs/components/modal)进行了封装，做了一些默认的配置及支持。

```javascript
//弹出confirm
this.$confirm("hello world");
//弹出alert,支持html，vm代表当前的vue component
this.$confirm('<span class="text-danger" @click="vm.alert()">hello world</span>');
this.$confirm("hello world", {
      variant: 'error/danger/info...',
      title: 'Confirmation',
      size: 'sm',
      buttonSize: 'sm',
      okVariant: 'success',
      headerClass: 'p-2 border-bottom-0',
      footerClass: 'p-2 border-top-0',
      centered: true
}).then(()=>{
    console.log("ok");
}).catch(()=>{
    console.log("error");
});
//alert与下面的调用基本等价
this.$bvModal.msgBoxConfirm(message, options);
```

### 列表页

列表页的js有一个dataProvider来控制整个页面（注意：它与php的DataProvider并不是同一个东西）。

#### 1.输入时刷新数据

我们可以在form-item上加一个事件侦听，这样当我们输入时，下面的表格可以自动搜索并刷新。

```markup
<form-item :model="dp.searchModel" attr="name" @input="refresh('')"></form-item>
```

#### &#x20;2. 一些DataProvider的API

{% code title="index.js" %}

```javascript
// 生成DataProvider
this.dp = ledap.App.getWebDataProvider({
    httpOptions:{
        url:'/test/xxx',
        params:'ddd',
    }
    // 这个可以不填。填了之后，数据会以model.id进行判重，防止重复的数据展示
    primaryKey: 'id',
    // 可以不填，代表两个请求间的最小间隔，这在suggestion, http请求上极为有用。
    // 0 代表无间隔
    timeWait : 600,
});

// dataprovider同四部分组成。
// searchModel代表dp的搜索参数
console.log(this.dp.searchModel)
// 数据
console.log(this.dp.models);
// 列表页的排序器
console.log(this.dp.sort);
// 列表页的分页器
console.log(this.dp.pager);

// 修改参数并刷新
this.dp.searchModel.name = 'xxx';
this.dp.refresh();
// 也可以用如下的函数达到同样的效果
this.dp.changeParams({name:"xxx"});

// 修改页码
this.dp.changePage(page)
this.dp.nextPage();
this.dp.prePage();

// 我们有时候希望实现h5类似的上拉刷新和下拉刷新效果
this.dp.refresh("header");
this.dp.refersh("footer");

// 我们可能希望排序
this.dp.sort = "id, -name";
this.dp.refresh();
// 我们也可以直接通过函数达到同样的效果
this.dp.setSort("id,-name");

// 本地排序（不走http请求）
this.dp.sortModels("name", asc=true);

// 可以通过isLoading来判断当前的加载状态
this.dp.isLoading
// 我们也可以通过事件来做一些事情。
this.dp.on(ledap.WebDataProvider.EVENT_BEFOREGETDATA, function(){
});
this.dp.on(ledap.WebDataProvider.EVENT_AFTERGETDATA, function(){
});
```

{% endcode %}

#### &#x20;3. grid

grid是一个ledap组件，可以通过传入columns和dataProvider来生成表格

{% code title="index.js" %}

```javascript
columns : {
    'id',
    {
        'attribute' : 'id',
        'label' : 'ID',
        //如果设置了sort,label是可以点击来排序的。
        'userSort' : true, 
    },
    {
        'attribute' : 'name',
        'value' : function() {
            // vm是当前的vue Component
            // 在模板中我们能使用value,model, index, attribute 和 dataProvider 
            return '<a @click="vm.xxx(model)"></a>';
        },
        'format': 'html',
    }
}
```

{% endcode %}

如果你不满意默认的内容，你可以通过Vue Scoped slot来修改内容。组件会传输四个变量到scoped slot中：

* **model**. 当前的model, 代表grid的一行。
* **column**. 当前的colomn, 代表grid中的一列。
* **index**.  model在dp.models中的索引。dp.model\[index] == model。
* **value**. cloumn与model计算出来的结果。&#x20;

{% code title="index.php" %}

```markup
<grid class="table" :data-provider="dp" :columns="columns">
    <template v-slot:label="p">
        <th class="xxx">{{p.value}}{{p.model}}{{p.index}}{{p.column}}</th>
    </template>

    <template v-slot:default="p">
        <td class="xxx">{{p.value}}{{p.model}}{{p.index}}{{p.column}}</td>
    </template>
</grid>
```

{% endcode %}

你也可以使用dataProvider自己画view或使用list，而不是使用grid。

{% code title="index.php" %}

```markup
<input type="xxx" v-model="dp.searchModel.name" @input="dp.refresh('')" />
<div v-for="model in dp.models">
    <div class="">{{model.id}}</div>
    <div class="xxx">{{model.img}}</div>
</div>
<div class="pager">
    <div>total:{{dp.pager.currentPage}}|{{dp.pager.totalCount}}</div>
    <a @click="dp.prePage()">PrePage</a>
    <a @click="dp.nextPage()">NextPage</a>
</div>
```

{% endcode %}

### View\&Update\&Create

在页面中有type这个参数，可以被用来控制页面的显示。它可以是view, update或create。

#### 1.model

model与yii2的form Model 对应。基本用法如下：

{% code title="view\.js" %}

```javascript
//生成一个model
let model = ledap.App.getModel(data);

// 修改model的值
model.name = "xxx";
// 获取model某个属性的label
model.getAttributeLabel("name");
// 获取model某个属性的hit
model.getAttributeHint("name");
// 获取model某个属性的error
model.getErrors("name");
// 获取model某个属性的第一条错误
model.getFirstError("name");
// 获取model的所有错误
model.getErrors();

// 校验model的值.如果不正确，会返回false.
// 我们可以使用getErrors来展示错误
model.validate();

```

{% endcode %}

#### 2.detail

detail组件与grid组件类似。我们可以使用columns来展示一个detail，它的写法也与grid一致。

{% code title="view\.js" %}

```javascript
columns : {
    'id',
    {
        'attribute' : 'id',
        'label' : 'ID',
        //if use sort, we can click header to sort table
        'userSort' : true, 
    },
    {
        'attribute' : 'name',
        'value' : function() {
            // vm refer to current vue component
            // value,model, index, attribute and dataProvider can be use in the template
            return '<a @click="vm.xxx(model)">test</a>';
        },
        'format': 'html',
    }
}
```

{% endcode %}

与grid一样，我们也可以使用scoped slot来修改默认的页面。&#x20;

#### 3. form-item

当我们想要展示form时，form-item非常重要。它由四部分组成：

* label
* hint
* input
* error

we cant use like this:

{% code title="\_form.php" %}

```markup
<!-- 
    常规输入框
    validator: 什么时候去校验数据
    tag: 组件的tag，默认为div
-->
<form-item :model="model" attr="name" :validator="['input', 'blur', 'focus']" :tag="div">
</form-item>

<!-- 我们可以使有scoped slot来修改默认的页面-->
<form-item :model="model" attr="name">
    <template v-slot:label="p">
        <label> {{p.model.getAttributeLabel(p.attr)}}{{p.model.isRequired(p.attr) ? '*' : ''}}</label>
    </template>
    <template v-slot="p">
        <baseinput type="password" v-bind="p"></baseinput>
    </template>
    <template v-slot:error="p">
        <p v-show="p.showError">{{p.showError}}</p>
    </template>
</form-item>

<!-- 一些其它输入框 -->
<!-- 
    groupinput 需要DictValidator。你应该在yii2的model中添加如下的rule来使用:
    ['name', \ethercap\common\validators\DictValidator::className(), [0 => 'xxx', 1=> 'xxx', ...]],
    另外：dropdown也需要DictValidator.
    ajax select 你可以参见下一节。
-->
<form-item :model="model" attr="name">
    <template v-slot="p">
        <groupinput v-bind="p"></groupinput>
    </template>
</form-item>
<form-item :model="model" attr="name">
    <template v-slot="p">
        <dropdown v-bind="p"></dropdown>
    </template>
</form-item>
```

{% endcode %}

有时，我们想用其它第三方写的vue组件，所以，我在这个包里写了一个示例。我使用了 [vue2-datepicker](https://www.npmjs.com/package/vue2-datepicker)来示例.

{% code title="\_form.php" %}

```php
<?php
// DatePickerAsset中只是写了js的地址，我们也可以直接register来引入js
// $this->registerJs("http://xxxx.js", ['depends' => '\ethercap\ledap\assets\VueAsset'])
\ethercap\ledap\assets\DatePickerAsset::register($this);
?>

<form-item :model="model" attr="time">
    <template v-slot="p">
        <date-picker v-model="p.model[p.attr]" :value-type="'format'"></date-picker>
    </template>
</form-item>

```

{% endcode %}

另外，我们需要在Vue中注册这个组件：

{% code title="view\.js" %}

```javascript
Vue.component("date-picker", DatePicker.default);
```

{% endcode %}

### 其它组件

#### 1.select2 & SearchAction

有时，我们需要一个类似于select2的ajax suggestion。这个包也为你提供了：

* SearchAction: 一个 php Action 来处理搜索的请求.

```php
<?php
namespace frontend\controllers;
use Yii;
class xxController extends Controller
{
    // ....
    
    public function actions()
    {
        return [
            'search' => [
                'class' => \ethercap\ledap\actions\SearchAction::className(),
                'processQuery' => function($model) {
                     $query = xxx::find()->select(['id', 'name as text'])->asArray();
                     if($model->id) {
                         return $query->where(['id' => $model->id]);
                     }
                     if($model->keyword){
                         $query->andWhere(['like', 'name', $model->keyword]);                         
                     }
                     return $query;
                },
                // 接口返回的数据字段。默认如下，可以不写。
                $dataConfig => [
                    'id',
                    'text'
                ]
            ],
        ];
    } 

}
```

after all this config, we have an api "/xx/search" to search data from database.

在这些配置完成后，我们就拥有了一个api——"/xx/search"来从数据库中搜索数据。

* select2 component: 一个vue组件来发起http请求。

{% code title="\_form.php" %}

```markup
<form-item :model="model" attr="search">
    <template v-slot="p">
        <select2 v-bind="p" :data-provider="dp"></select2>
    </template>
</form-item>
```

{% endcode %}

{% code title="view\.js" %}

```javascript
{
    data:{
        dp: App.ledap.getWebDp({
            httpOptions:{
                "url" : '/xx/search'
            }
        });
    }
}
```

{% endcode %}

#### 2.upload & UploadAction

我们在php中引入js等依赖

```php
<?php
ethercap\ledap\assets\UploadAsset::register($this); 
```

在页面调用组件上传即可。

{% code title="\_form.js" %}

```javascript
<form-item class="form-group" :model="model" attr="attr">
    <template v-slot="p">
        <uploader post-action='http://xxx.com/upload' v-model="p.model[p.attr]"></uploader>
    </template>
</form-item>
```

{% endcode %}

后端可以通过很简单的方式来处理, 文件在$\_FILE中。

在数据库中，该数据对应的结构为:

```
$model->attr=json_encode([
    ['name'=>'xxxx', 'url' => 'xxx'],
    ['name'=>'xxxx', 'url' => 'xxx'],
]);
```

### 高阶应用

#### 自己实现vue组件/Ledap组件

在页面中，如果有一些元素总是重复，我们可以书写成组件方便复用，我们可以直接书写Vue组件，参见[vue组件](https://cn.vuejs.org/v2/guide/components-registration.html)。

ledap组件跟vue组件没有本质区别，但是引入了继承，下面我们来看一个例子：

我们一直使用formItem来做为form的包装器，现在我们想做一个formItem1, 但是把label和input的位置对调，我们可以这样做：

```javascript
//我们定义了一个叫form-item1的组件，它继承于form-item，除了template不同，其它的所有都与form-item一样（data, props, methods等）
ledap.App.getTheme().addComponent({
    name : 'form-item1',
    template:`<component :is="tag" class="form-group" :class="{'has-error':showError}">
             <div class="col-sm-9">
                 <slot :model="model" :attr="attr" :validate="validate" :inputListeners="inputListeners">
                     <input class="form-control" :name="attr" :value="model[attr]" :placeholder="model.getAttributeHint(attr)"  v-on="inputListeners" />
                 </slot>
                 <slot name="error" :model="model" :attr="attr" :showError="showError">
                     <p v-show="showError" class="help-block">{{showError}}</p>
                 </slot>
             </div>
             <slot name="label" :model="model" :attr="attr">
                 <label class="col-sm-3 control-label"> {{model.getAttributeLabel(attr)}}{{model.isRequired(attr) ? '*' :       ''}}</label>
             </slot>
     </component>`,
},'form-item');
```

#### 这样，我们可以在页面上看到效果了：

```markup
<!-- 注意，别忘记在js上注册ledap.App.getTheme().register(['form-item1'], Vue) -->
<form-item1 :model="model" :attr="xxx"> </form-item1>
```

#### 改用其它css框架

### Tips

1. 你需要了解Vue.
2. 尽量少用yii2 widget.
3. 所有你使用的组件都需要先注册。App.register(\['xxx'], Vue);
4. 你可以使用其它组件。
5. 你可以通过修改AppAsset来替换全局模板或http请求

这个包，前后端并不是完全分离的。如果你想要使用npm来实现完全分离，直接使用ledap前端框架即可。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ethercap.gitbook.io/ledap/zh-cn/yii2-ledap.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
